diff --git a/.husky/commit-msg b/.husky/commit-msg index b7acfd6..adb4471 100644 --- a/.husky/commit-msg +++ b/.husky/commit-msg @@ -3,6 +3,7 @@ echo "🔍 Validando mensaje de commit..." # Expresión regular mejorada: # tipo(scope opcional): descripción mínima de 10 caracteres +# git commit -m "feat(funcionalidad): "Nueva funcionalidad para el sistema" commit_regex="^(feat|fix|docs|style|refactor|perf|test|chore|ci|build|revert)(\([a-zA-Z0-9_-]+\))?: ?.{10,}$" commit_msg=$(cat "$1") diff --git a/.husky/pre-commit b/.husky/pre-commit index d52e30d..943f917 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,29 +1,20 @@ #!/bin/sh +# Cargar NVM (necesario en entornos donde Husky no hereda el PATH completo) +export NVM_DIR="$HOME/.nvm" +# Esto carga nvm +[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" +# Esto carga nvm para bash >=0.4.0 +[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" + echo "🔍 Verificando archivos antes del commit..." +echo "📦 Formateando código con Prettier..." -# Ejecutar pruebas con yarn -# echo "🧪 Ejecutando tests..." -# if ! yarn test; then -# echo "❌ Tests fallaron. Corrige los errores antes de hacer commit." -# exit 1 -# fi +yarn prettier 'src/**/*.{js,ts,jsx,tsx,json}' server.js package.json --write || exit 1 -# Formatear código con Prettier -echo "🚀 Ejecutando Prettier..." -if ! yarn prettier . --write; then - echo "❌ Error al ejecutar Prettier." - exit 1 -fi # Agregar los archivos modificados después del formateo git add . -# Verificar que el código esté actualizado antes del push -echo "🔄 Verificando que tu código esté actualizado..." -if ! git pull; then - echo "❌ Error al actualizar el código. Revisa los conflictos." - exit 1 -fi -echo "✅ Todo listo para el commit. 🎉" +echo "✅ Todo listo para el commit. 🎉" \ No newline at end of file diff --git a/.husky/pre-push b/.husky/pre-push new file mode 100644 index 0000000..cdb10cb --- /dev/null +++ b/.husky/pre-push @@ -0,0 +1,16 @@ + +#!/bin/sh + +BRANCH=$(git symbolic-ref --short HEAD) + +if [ "$BRANCH" = "staging" ]; then + echo "📍 Estás en la rama 'staging'. Ejecutando pruebas..." + echo "🧪 Ejecutando tests con servidor..." + + if ! yarn test:with-server; then + echo "❌ Tests fallaron. Corrige los errores antes de hacer commit." + exit 1 + fi +else + echo "🚫 No estás en la rama 'staging'. No se ejecutan los tests." +fi diff --git a/package.json b/package.json index 6f363c5..40fa323 100644 --- a/package.json +++ b/package.json @@ -4,12 +4,13 @@ "description": "Backend API for the project, using Node.js, Express, and MySQL.", "main": "server.js", "scripts": { - "start": "node server.js", - "dev": "node --watch server.js", - "format": "prettier . --write", + "start": "nodemon server.js", + "dev": "nodemon server.js", + "format": "prettier 'src/**/*.{js,ts,jsx,tsx,json}' server.js package.json --write", "prepare": "husky", "test": "jest", - "test:watch": "jest --watchAll" + "test:watch": "jest --watchAll", + "test:with-server": "concurrently --kill-others --success first \"yarn dev\" \"yarn test\"" }, "license": "MIT", "type": "module", @@ -51,11 +52,13 @@ "devDependencies": { "@eslint/js": "^9.26.0", "@faker-js/faker": "^9.7.0", + "concurrently": "^9.2.0", "dotenv": "^16.5.0", "eslint": "^9.26.0", "frisby": "^2.1.3", "globals": "^16.1.0", "jest": "^29.7.0", + "nodemon": "^3.1.10", "prettier": "^3.5.3" } } diff --git a/server.js b/server.js index cacde75..b5e41a2 100644 --- a/server.js +++ b/server.js @@ -5,13 +5,13 @@ import morgan from "morgan"; import { createServer } from "node:http"; import { setupSwagger } from "./src/config/swaggerConfig.js"; -import { corsOptions } from "./src/middleware/cors.js"; -import { errorHandler } from "./src/middleware/errorHandler.js"; -import { router } from "./src/router/index.js"; +import { corsOptions } from "./src/middleware/cors.middleware.js"; +import { errorHandler } from "./src/middleware/errorHandler.middleware.js"; +import { router } from "./src/routes/index.js"; // Datos del proyecto const projectInfo = { - name: "CRM Kinder Garden", + name: "CRM Kinder Garden Backend", description: "CRM para Gestión y Administración de una escuela", version: "1.0.0", authorName: "Erick Gonzalez", @@ -72,6 +72,6 @@ server.on("error", (error) => { server.listen(currentPort, () => { console.log( - `🟢 Server is listening on port localhost:${server.address().port}`, + `🟢 API funcionando correctamente, servidor corriendo en el puerto localhost:${server.address().port}`, ); }); diff --git a/src/__tests__/users/apiCreateUser.test.js b/src/__tests__/users/apiCreateUser.test.js index 98a821f..387a9b0 100644 --- a/src/__tests__/users/apiCreateUser.test.js +++ b/src/__tests__/users/apiCreateUser.test.js @@ -1,6 +1,6 @@ require("dotenv").config(); const frisby = require("frisby"); -const { createTokenTesting } = require("../../helpers/apiCreateToken"); +const { createTokenTesting } = require("../../helpers/apiCreateToken.helpers"); const BASE_URL = process.env.BASE_URL; @@ -16,7 +16,7 @@ describe("✅ Prueba para crear a un nuevo usuario", () => { }, }, }) - .post(`${BASE_URL}/crear-usuario`, { + .post(`${BASE_URL}/users`, { nameUser: "apiTESTCreate", email: uniqueEmail, password: "123456788u02kljfLK", diff --git a/src/__tests__/users/apiDeleteUser.test.js b/src/__tests__/users/apiDeleteUser.test.js index f53d15f..b1e1860 100644 --- a/src/__tests__/users/apiDeleteUser.test.js +++ b/src/__tests__/users/apiDeleteUser.test.js @@ -1,6 +1,6 @@ require("dotenv").config(); const frisby = require("frisby"); -const { createTokenTesting } = require("../../helpers/apiCreateToken"); +const { createTokenTesting } = require("../../helpers/apiCreateToken.helpers"); const { randomUUID } = require("node:crypto"); const BASE_URL = process.env.BASE_URL; @@ -17,7 +17,7 @@ describe("✅ Prueba para eliminar un usuario por su id", () => { }, }, }) - .del(`${BASE_URL}/eliminar-usuario/${id}`) + .del(`${BASE_URL}/users/${id}`) .then((res) => { // console.log("🔎 STATUS:", res.status); // console.log("🔎 RESPONSE:", res.json); diff --git a/src/__tests__/users/apiListUsers.test.js b/src/__tests__/users/apiListUsers.test.js index a833761..bd54c04 100644 --- a/src/__tests__/users/apiListUsers.test.js +++ b/src/__tests__/users/apiListUsers.test.js @@ -1,6 +1,6 @@ require("dotenv").config(); const frisby = require("frisby"); -const { createTokenTesting } = require("../../helpers/apiCreateToken"); +const { createTokenTesting } = require("../../helpers/apiCreateToken.helpers"); const Joi = frisby.Joi; const BASE_URL = process.env.BASE_URL; @@ -16,13 +16,12 @@ describe("✅ Prueba para la lista de usuarios", () => { }, }, }) - .get(`${BASE_URL}/lista-de-usuarios/Activo?correo=normal&rol=admin`) + .get(`${BASE_URL}/users?status=Activo&correo=normal&rol=admin`) .then((res) => { // console.log("🔎 STATUS:", res.status); // console.log("🔎 RESPONSE:", res.json); expect([200, 400, 429, 500]).toContain(res.status); }) - .expect("header", "Content-Type", /application\/json/) .expect("jsonTypes", { success: Joi.boolean().required(), data: Joi.array().items( diff --git a/src/__tests__/users/apiRegisterUser.test.js b/src/__tests__/users/apiRegisterUser.test.js index 554c59f..5c5d594 100644 --- a/src/__tests__/users/apiRegisterUser.test.js +++ b/src/__tests__/users/apiRegisterUser.test.js @@ -1,6 +1,6 @@ require("dotenv").config(); const frisby = require("frisby"); -const { createTokenTesting } = require("../../helpers/apiCreateToken"); +const { createTokenTesting } = require("../../helpers/apiCreateToken.helpers"); const BASE_URL = process.env.BASE_URL; @@ -16,7 +16,7 @@ describe("✅ Prueba para registrar un usuario", () => { }, }, }) - .post(`${BASE_URL}/registrar-usuario`, { + .post(`${BASE_URL}/users/auth/register`, { nameUser: "apiTESTRegister", email: uniqueEmail, password: "123456788u02kljfLK", diff --git a/src/__tests__/users/apiSearchUser.test.js b/src/__tests__/users/apiSearchUser.test.js index 5dd6657..faf3362 100644 --- a/src/__tests__/users/apiSearchUser.test.js +++ b/src/__tests__/users/apiSearchUser.test.js @@ -1,6 +1,6 @@ require("dotenv").config(); const frisby = require("frisby"); -const { createTokenTesting } = require("../../helpers/apiCreateToken"); +const { createTokenTesting } = require("../../helpers/apiCreateToken.helpers"); const Joi = frisby.Joi; const BASE_URL = process.env.BASE_URL; @@ -16,13 +16,12 @@ describe("✅ Prueba para buscar un usuario", () => { }, }, }) - .get(`${BASE_URL}/busqueda-usuario/muke`) + .get(`${BASE_URL}/users/search?email=muke7881@gmail.com`) .then((res) => { // console.log("🔎 STATUS:", res.status); // console.log("🔎 RESPONSE:", res.json); expect([200, 400, 429, 500]).toContain(res.status); }) - .expect("header", "Content-Type", /application\/json/) .expect("jsonTypes", { success: Joi.boolean().required(), data: Joi.array().items( diff --git a/src/__tests__/users/apiUpdateUser.test.js b/src/__tests__/users/apiUpdateUser.test.js index f4a72c0..55f46dc 100644 --- a/src/__tests__/users/apiUpdateUser.test.js +++ b/src/__tests__/users/apiUpdateUser.test.js @@ -1,6 +1,6 @@ require("dotenv").config(); const frisby = require("frisby"); -const { createTokenTesting } = require("../../helpers/apiCreateToken"); +const { createTokenTesting } = require("../../helpers/apiCreateToken.helpers"); const BASE_URL = process.env.BASE_URL; @@ -16,13 +16,12 @@ describe("✅ Prueba para actualizar un usuario", () => { }, }, }) - .put(`${BASE_URL}/actualizar-usuario`, { + .put(`${BASE_URL}/users/4901398e-2672-11f0-b8d7-d843ae0db894`, { nameUser: "Roberta_Rath-Hoppe-Furth", email: uniqueEmail, password: "129sdnKLMF@asfd11", role: "user", accountStatus: "Inactivo", - id: "1aa52951-2f05-11f0-9a7d-d843ae0db894", }) .then((res) => { // console.log("🔎 STATUS:", res.status); diff --git a/src/config/swaggerConfig.js b/src/config/swaggerConfig.js index 46f976b..e0bc127 100644 --- a/src/config/swaggerConfig.js +++ b/src/config/swaggerConfig.js @@ -42,7 +42,7 @@ const swaggerDefinition = { const options = { swaggerDefinition, - apis: [resolve(__dirname, "../router/*.js")], + apis: [resolve(__dirname, "../routes/*.js")], }; export const swaggerDocument = swaggerJsdoc(options); diff --git a/src/controllers/catAssetsControllers.js b/src/controllers/catAssetsControllers.js index 5cb3dbc..e3e1ae9 100644 --- a/src/controllers/catAssetsControllers.js +++ b/src/controllers/catAssetsControllers.js @@ -1,4 +1,4 @@ -import { connectionQuery } from "../helpers/connection.helper.js"; +import { connectionQuery } from "../helpers/connection.helpers.js"; import { methodCreated, methodError, diff --git a/src/controllers/catInventarioControllers.js b/src/controllers/catInventarioControllers.js index 99317f8..d3ee599 100644 --- a/src/controllers/catInventarioControllers.js +++ b/src/controllers/catInventarioControllers.js @@ -1,4 +1,4 @@ -import { connectionQuery } from "../helpers/connection.helper.js"; +import { connectionQuery } from "../helpers/connection.helpers.js"; import { methodCreated, methodError, diff --git a/src/controllers/catSuppliesControllers.js b/src/controllers/catSuppliesControllers.js index ba6bd53..3acb6ed 100644 --- a/src/controllers/catSuppliesControllers.js +++ b/src/controllers/catSuppliesControllers.js @@ -1,4 +1,4 @@ -import { connectionQuery } from "../helpers/connection.helper.js"; +import { connectionQuery } from "../helpers/connection.helpers.js"; import { methodCreated, methodError, diff --git a/src/controllers/googleControllers.js b/src/controllers/googleControllers.js deleted file mode 100644 index 5e75b64..0000000 --- a/src/controllers/googleControllers.js +++ /dev/null @@ -1,152 +0,0 @@ -import dotenv from "dotenv"; -import crypto from "node:crypto"; - -import { connectionQuery } from "../helpers/connection.helper.js"; -import { createTokenGoogle } from "../helpers/jwtGoogle.js"; -import { lastLogin } from "../helpers/userLastLogin.js"; -import { googleClient } from "../lib/clientGoogle.js"; -import { - methodCreated, - methodError, - methodForbidden, - methodIncorrect, - methodOK, -} from "../server/serverMethods.js"; - -dotenv.config(); - -const loginGoogle = async (req, res) => { - const { credential } = req.body; - - if (!credential) { - return methodIncorrect(req, res, "ID Token no proporcionado"); - } - - try { - const ticket = await googleClient.verifyIdToken({ - idToken: credential, - audience: process.env.CLIENT_ID_GOOGLE, - }); - - const payload = ticket.getPayload(); - - // Extraer datos del payload verificado - const googleId = payload["sub"]; - const email = payload["email"]; - const name = payload["name"]; - const picture = payload["picture"]; - - if (!googleId || !email) { - // El token es válido pero falta info crucial, caso raro pero posible - return methodIncorrect( - req, - res, - "Información esencial del usuario de Google no encontrada", - ); - } - - const queryCheckUser = `SELECT * FROM users WHERE Email = ?`; - const existingUsers = await connectionQuery(queryCheckUser, [email]); - - // Variable para almacenar el usuario encontrado o creado - let user = null; - let isNewUser = false; - - if (existingUsers.length > 0) { - user = existingUsers[0]; - - // Si el usuario ya existe con método local ('normal'), no puede ingresar con Google - if (user.AccountType === "normal") { - return methodForbidden( - req, - res, - "Este correo ya se encuentra registrado por email y contraseña normal", - ); - } - } else { - // Si el usuario NO existe en la base de datos, lo creamos - isNewUser = true; - const newUserId = crypto.randomUUID(); - - const queryInsertByGoogle = ` - INSERT INTO users (ID, NameUser, Email, Password, ProfilePicture, AccountType, AccountStatus) - VALUES (?, ?, ?, NULL, ?, 'google', 'Inactivo')`; - const queryParamsInsertGoogle = [newUserId, name, email, picture]; - - const result = await connectionQuery( - queryInsertByGoogle, - queryParamsInsertGoogle, - ); - - if (result.affectedRows === 0) { - // Esto no debería pasar si no hubo error, pero es una verificación de seguridad - return methodError( - req, - res, - "Error al crear el nuevo usuario de google en la base de datos.", - ); - } - - // Recuperar los datos completos del usuario recién creado (incluyendo Role por defecto, etc.) - const queryGetUser = `SELECT ID, NameUser, Email, ProfilePicture, Role, AccountType, AccountStatus FROM users WHERE ID = ?`; - const newUserRows = await connectionQuery(queryGetUser, [newUserId]); - if (newUserRows.length === 0) { - // Error crítico si no se encuentra el usuario recién insertado - return methodError( - req, - res, - "Error interno: No se pudo recuperar el usuario recién creado.", - ); - } - user = newUserRows[0]; - } - - if (user.AccountStatus === "Inactivo") { - return methodForbidden( - req, - res, - "El usuario está inactivo, pida la reactivación a un administrador", - ); - } - - // Generar el token de tu aplicación (JWT) para el usuario identificado/creado - const appToken = createTokenGoogle(user); - // Actualizar la fecha de último login - await lastLogin(user.ID); - - // Enviar respuesta exitosa al frontend - const responseData = { - token: appToken, - user: { - ID: user.ID, - NameUser: user.NameUser, - Email: user.Email, - ProfilePicture: user.ProfilePicture, - Role: user.Role, - AccountType: user.AccountType, - AccountStatus: user.AccountStatus, - }, - }; - - if (isNewUser) { - return methodCreated(req, res, responseData); - } else { - return methodOK(req, res, responseData); - } - } catch (error) { - // Manejar errores, especialmente errores de verificación del token de Google - console.error("Error en loginGoogle:", error); - if (error.message && error.message.includes("Invalid token")) { - return methodIncorrect( - req, - res, - "ID Token de Google inválido o expirado.", - ); - } - methodError(req, res, error); // Manejador de error genérico - } -}; - -export default { - loginGoogle, -}; diff --git a/src/controllers/maestrosControllers.js b/src/controllers/maestrosControllers.js index a0146b4..4572e04 100644 --- a/src/controllers/maestrosControllers.js +++ b/src/controllers/maestrosControllers.js @@ -1,4 +1,4 @@ -import { connectionQuery } from "../helpers/connection.helper.js"; +import { connectionQuery } from "../helpers/connection.helpers.js"; // import { deleteUserByTeacherID } from "../helpers/teachersEliminatedStatus.js"; import { methodConflicts, diff --git a/src/controllers/padresControllers.js b/src/controllers/padresControllers.js index e21ea29..537f6ca 100644 --- a/src/controllers/padresControllers.js +++ b/src/controllers/padresControllers.js @@ -1,4 +1,4 @@ -import { connectionQuery } from "../helpers/connection.helper.js"; +import { connectionQuery } from "../helpers/connection.helpers.js"; import { methodConflicts, methodCreated, diff --git a/src/controllers/studentsController.js b/src/controllers/studentsController.js index c47b2f0..73516ca 100644 --- a/src/controllers/studentsController.js +++ b/src/controllers/studentsController.js @@ -1,4 +1,4 @@ -import { connectionQuery } from "../helpers/connection.helper.js"; +import { connectionQuery } from "../helpers/connection.helpers.js"; import { methodError, methodNotFound, diff --git a/src/controllers/token/functions/token.controllers.js b/src/controllers/token/functions/token.controllers.js new file mode 100644 index 0000000..b700849 --- /dev/null +++ b/src/controllers/token/functions/token.controllers.js @@ -0,0 +1,6 @@ +import { refreshToken } from "../../../helpers/jwt.helpers.js"; + +export const RefreshToken = async (token) => { + const refresh = await refreshToken(token); + return refresh; +}; diff --git a/src/controllers/token/index.js b/src/controllers/token/index.js new file mode 100644 index 0000000..6e238ba --- /dev/null +++ b/src/controllers/token/index.js @@ -0,0 +1 @@ +export * from "./functions/token.controllers.js"; diff --git a/src/controllers/users/functions/deleteUser.js b/src/controllers/users/functions/deleteUser.controllers.js similarity index 100% rename from src/controllers/users/functions/deleteUser.js rename to src/controllers/users/functions/deleteUser.controllers.js diff --git a/src/controllers/users/functions/editUser.js b/src/controllers/users/functions/editUser.controllers.js similarity index 100% rename from src/controllers/users/functions/editUser.js rename to src/controllers/users/functions/editUser.controllers.js diff --git a/src/controllers/users/functions/insertUsers.js b/src/controllers/users/functions/insertUsers.controllers.js similarity index 100% rename from src/controllers/users/functions/insertUsers.js rename to src/controllers/users/functions/insertUsers.controllers.js diff --git a/src/controllers/users/functions/listUsers.js b/src/controllers/users/functions/listUsers.controllers.js similarity index 100% rename from src/controllers/users/functions/listUsers.js rename to src/controllers/users/functions/listUsers.controllers.js diff --git a/src/controllers/users/functions/login.js b/src/controllers/users/functions/login.controllers.js similarity index 100% rename from src/controllers/users/functions/login.js rename to src/controllers/users/functions/login.controllers.js diff --git a/src/controllers/users/functions/loginGoogle.controllers.js b/src/controllers/users/functions/loginGoogle.controllers.js new file mode 100644 index 0000000..01d3e8a --- /dev/null +++ b/src/controllers/users/functions/loginGoogle.controllers.js @@ -0,0 +1,6 @@ +import { loginGoogleService } from "../../../services/users/index.js"; + +export const loginFromGoogle = async (credential) => { + const { responseData, isNewUser } = await loginGoogleService(credential); + return { responseData, isNewUser }; +}; diff --git a/src/controllers/users/functions/registerUser.js b/src/controllers/users/functions/registerUser.controllers.js similarity index 100% rename from src/controllers/users/functions/registerUser.js rename to src/controllers/users/functions/registerUser.controllers.js diff --git a/src/controllers/users/functions/searchUser.js b/src/controllers/users/functions/searchUser.controllers.js similarity index 100% rename from src/controllers/users/functions/searchUser.js rename to src/controllers/users/functions/searchUser.controllers.js diff --git a/src/controllers/users/index.js b/src/controllers/users/index.js index 6644b9d..c73f8fe 100644 --- a/src/controllers/users/index.js +++ b/src/controllers/users/index.js @@ -1,8 +1,9 @@ -export * from "./functions/deleteUser.js"; -export * from "./functions/editUser.js"; -export * from "./functions/insertUsers.js"; -export * from "./functions/listUsers.js"; -export * from "./functions/login.js"; -export * from "./functions/registerUser.js"; -export * from "./functions/searchUser.js"; +export * from "./functions/deleteUser.controllers.js"; +export * from "./functions/editUser.controllers.js"; +export * from "./functions/insertUsers.controllers.js"; +export * from "./functions/listUsers.controllers.js"; +export * from "./functions/login.controllers.js"; +export * from "./functions/loginGoogle.controllers.js"; +export * from "./functions/registerUser.controllers.js"; +export * from "./functions/searchUser.controllers.js"; export * from "./index.js"; diff --git a/src/helpers/apiCreateToken.js b/src/helpers/apiCreateToken.helpers.js similarity index 100% rename from src/helpers/apiCreateToken.js rename to src/helpers/apiCreateToken.helpers.js diff --git a/src/helpers/connection.helper.js b/src/helpers/connection.helpers.js similarity index 100% rename from src/helpers/connection.helper.js rename to src/helpers/connection.helpers.js diff --git a/src/helpers/findUserByEmail.js b/src/helpers/findUserByEmail.helpers.js similarity index 87% rename from src/helpers/findUserByEmail.js rename to src/helpers/findUserByEmail.helpers.js index 97219a3..df6e5bd 100644 --- a/src/helpers/findUserByEmail.js +++ b/src/helpers/findUserByEmail.helpers.js @@ -1,4 +1,4 @@ -import { connectionQuery } from "./connection.helper.js"; +import { connectionQuery } from "./connection.helpers.js"; // Esta funcion se ocupa para verificar si un correo ya se ha ingresado anteriormente y si es asi, va mandar un error que el correo ya se ingreso // y fallara la insercion regresando un estado Conflict diff --git a/src/helpers/getUserByEmail.js b/src/helpers/getUserByEmail.helpers.js similarity index 73% rename from src/helpers/getUserByEmail.js rename to src/helpers/getUserByEmail.helpers.js index a5053a2..f54106a 100644 --- a/src/helpers/getUserByEmail.js +++ b/src/helpers/getUserByEmail.helpers.js @@ -1,7 +1,7 @@ -import { connectionQuery } from "./connection.helper.js"; +import { connectionQuery } from "./connection.helpers.js"; // Esta funcion es para regresar en la peticion de la solicitud cuando es exitosa, -// regresa los campos seleccioandos en la consulta +// regresa los campos seleccionados en la consulta export const getUserByEmail = async (email) => { const query = `SELECT NameUser, Email, Role, AccountStatus FROM users WHERE Email = ?`; const result = await connectionQuery(query, [email]); diff --git a/src/helpers/getUserByEmailAndId.js b/src/helpers/getUserByEmailAndId.helpers.js similarity index 88% rename from src/helpers/getUserByEmailAndId.js rename to src/helpers/getUserByEmailAndId.helpers.js index a9c7df6..f7782d8 100644 --- a/src/helpers/getUserByEmailAndId.js +++ b/src/helpers/getUserByEmailAndId.helpers.js @@ -1,4 +1,4 @@ -import { connectionQuery } from "./connection.helper.js"; +import { connectionQuery } from "./connection.helpers.js"; // Esta funcion se ocupa para verificar si un correo ya se ha ingresado anteriormente cuando se edita a un usuario y si es asi, // va mandar un error que el correo ya se ingreso diff --git a/src/helpers/jwt.helpers.js b/src/helpers/jwt.helpers.js new file mode 100644 index 0000000..4271afb --- /dev/null +++ b/src/helpers/jwt.helpers.js @@ -0,0 +1,62 @@ +import { addDay, addHour } from "@formkit/tempo"; +import dotenv from "dotenv"; +import jwt from "jsonwebtoken"; + +dotenv.config(); + +const secret = process.env.JWT_SECRET; + +export const createToken = (user) => { + // Duracion de 2 minutos para probar que el toke se expire correctamente + // const expirationDate = addMinute(new Date(), 2); + + const expirationDate = addHour(new Date(), 12); + + const expirationTime = Math.floor(expirationDate.getTime() / 1000); + + const payload = { + id: user.id, + nameUser: user.nameUser, + email: user.email, + profilePicture: user.profilePicture, + role: user.role, + accountType: user.accountType, + lastLogin: user.lastLogin, + accountStatus: user.accountStatus, + iat: Math.floor(Date.now() / 1000), + exp: expirationTime, + }; + return jwt.sign(payload, secret); +}; + +export const refreshToken = (token) => { + const decoded = jwt.verify(token, secret); + + if (decoded.accountStatus === "Inactivo") { + throw { + statusCode: 403, + message: "Cuenta inactiva, porfavor contacta al administrador.", + code: "ACCOUNT_INACTIVE", + details: + "La cuenta esta desactivada, no se puede generar un nuevo token.", + }; + } + + const expirationDate = addDay(new Date(), 20); + + const expirationTime = Math.floor(expirationDate.getTime() / 1000); + + const payload = { + id: decoded.id, + nameUser: decoded.nameUser, + email: decoded.email, + profilePicture: decoded.profilePicture, + role: decoded.role, + accountType: decoded.accountType, + lastLogin: decoded.lastLogin, + accountStatus: decoded.accountStatus, + iat: Math.floor(Date.now() / 1000), + exp: expirationTime, + }; + return jwt.sign(payload, secret); +}; diff --git a/src/helpers/jwt.js b/src/helpers/jwt.js deleted file mode 100644 index 760d62f..0000000 --- a/src/helpers/jwt.js +++ /dev/null @@ -1,30 +0,0 @@ -import { addHour } from "@formkit/tempo"; -import dotenv from "dotenv"; -import jwt from "jsonwebtoken"; - -dotenv.config(); - -const secret = process.env.JWT_SECRET; - -export const createToken = (user) => { - // Duracion de 2 minutos para probar que el toke se expire correctamente - // const expirationDate = addMinute(new Date(), 2); - - const expirationDate = addHour(new Date(), 12); - - const expirationTime = Math.floor(expirationDate.getTime() / 1000); - - const payload = { - id: user.id, - nameUser: user.nameUser, - email: user.email, - profilePicture: user.profilePicture, - role: user.role, - accountType: user.accountType, - lastLogin: user.lastLogin, - accountStatus: user.accountStatus, - iat: Math.floor(Date.now() / 1000), - exp: expirationTime, - }; - return jwt.sign(payload, secret); -}; diff --git a/src/helpers/jwtGoogle.js b/src/helpers/jwtGoogle.helpers.js similarity index 100% rename from src/helpers/jwtGoogle.js rename to src/helpers/jwtGoogle.helpers.js diff --git a/src/helpers/userLastLogin.js b/src/helpers/userLastLogin.helpers.js similarity index 76% rename from src/helpers/userLastLogin.js rename to src/helpers/userLastLogin.helpers.js index 373ab61..dadc4d1 100644 --- a/src/helpers/userLastLogin.js +++ b/src/helpers/userLastLogin.helpers.js @@ -1,4 +1,4 @@ -import { connectionQuery } from "./connection.helper.js"; +import { connectionQuery } from "./connection.helpers.js"; export const lastLogin = (userId) => { const updateUserLastLogin = `UPDATE users SET LastLogin = CURRENT_TIMESTAMP WHERE id = ?`; diff --git a/src/helpers/usersHelpers/rateLimitRequestUsers.js b/src/helpers/usersHelpers/rateLimitRequestUsers.js index 9b82c7a..4388d1c 100644 --- a/src/helpers/usersHelpers/rateLimitRequestUsers.js +++ b/src/helpers/usersHelpers/rateLimitRequestUsers.js @@ -1,4 +1,4 @@ -import { rateLimitRequest } from "../../middleware/rateLimitRequest.js"; +import { rateLimitRequest } from "../../middleware/rateLimitRequest.middleware.js"; // Example // rateLimitRequest(time to try again, limit each IP request per windows, messageRequest response) diff --git a/src/lib/clientGoogle.js b/src/lib/clientGoogle.lib.js similarity index 100% rename from src/lib/clientGoogle.js rename to src/lib/clientGoogle.lib.js diff --git a/src/middleware/cors.js b/src/middleware/cors.middleware.js similarity index 100% rename from src/middleware/cors.js rename to src/middleware/cors.middleware.js diff --git a/src/middleware/errorHandler.js b/src/middleware/errorHandler.middleware.js similarity index 72% rename from src/middleware/errorHandler.js rename to src/middleware/errorHandler.middleware.js index 70e1058..8cdb7f1 100644 --- a/src/middleware/errorHandler.js +++ b/src/middleware/errorHandler.middleware.js @@ -1,11 +1,11 @@ import crypto from "node:crypto"; -export const errorHandler = (err, req, res, next) => { +export const errorHandler = (err, request, response, next) => { const status = err.statusCode || 500; const timestamp = new Date().toISOString(); const errorId = crypto.randomUUID(); - - res.status(status).json({ + console.log(request); + response.status(status).json({ success: false, error: { message: err.message || "Se produjo un error inesperado.", @@ -16,8 +16,9 @@ export const errorHandler = (err, req, res, next) => { timestamp, errorId, stack: process.env.NODE_ENV === "production" ? undefined : err.stack, - path: req.originalUrl, - method: req.method, + path: request.originalUrl, + method: request.method, + query: request.query, }, }); }; diff --git a/src/middleware/loadChalk.js b/src/middleware/loadChalk.js deleted file mode 100644 index 01d2cab..0000000 --- a/src/middleware/loadChalk.js +++ /dev/null @@ -1,5 +0,0 @@ -// Función para cargar chalk dinámicamente -export const loadChalk = async () => { - const chalk = await import("chalk"); - return chalk.default; -}; diff --git a/src/middleware/rateLimitRequest.js b/src/middleware/rateLimitRequest.js deleted file mode 100644 index 5bc212d..0000000 --- a/src/middleware/rateLimitRequest.js +++ /dev/null @@ -1,19 +0,0 @@ -import { rateLimit } from "express-rate-limit"; - -import { methodTooManyRequests } from "../server/serverMethods.js"; - -export const rateLimitRequest = (time, limit, messageRequest) => { - return rateLimit({ - windowMs: time * 60 * 1000, - limit: limit, - handler: (req, res) => { - methodTooManyRequests( - req, - res, - messageRequest + `inténtelo nuevamente después de ${time} minuto(s)`, - ); - }, - standardHeaders: true, - legacyHeaders: false, - }); -}; diff --git a/src/middleware/rateLimitRequest.middleware.js b/src/middleware/rateLimitRequest.middleware.js new file mode 100644 index 0000000..03c84aa --- /dev/null +++ b/src/middleware/rateLimitRequest.middleware.js @@ -0,0 +1,19 @@ +import { request, response } from "express"; +import { rateLimit } from "express-rate-limit"; + +export const rateLimitRequest = (time, limit, messageRequest) => { + return rateLimit({ + windowMs: time * 60 * 1000, + limit: limit, + handler: (request, response, next) => { + next({ + statusCode: 429, + message: `${messageRequest}. Inténtelo nuevamente después de ${time} minuto(s).`, + code: "TOO_MANY_REQUESTS", + details: "Has excedido el número máximo de solicitudes permitidas.", + }); + }, + standardHeaders: true, + legacyHeaders: false, + }); +}; diff --git a/src/middleware/verificarToken.js b/src/middleware/verificarToken.js deleted file mode 100644 index 4ac3c0f..0000000 --- a/src/middleware/verificarToken.js +++ /dev/null @@ -1,40 +0,0 @@ -import dotenv from "dotenv"; -import jwt from "jsonwebtoken"; - -import { methodUnauthorized } from "../server/serverMethods.js"; - -dotenv.config(); - -export const verificarToken = (req, res, next) => { - const token = req.header("Authorization"); - - if (!token) { - return methodUnauthorized( - req, - res, - "Acceso no autorizado. Token no proporcionado.", - ); - } - - const bearerToken = token.split(" ")[1]; - if (!bearerToken) { - return methodUnauthorized( - req, - res, - "Acceso no autorizado. Token no proporcionado.", - ); - } - - try { - const secretKey = process.env.JWT_SECRET; - const decoded = jwt.verify(bearerToken, secretKey); - req.usuario = decoded; - next(); - } catch (error) { - return methodUnauthorized( - req, - res, - `Acceso no autorizado. Token inválido ${error}`, - ); - } -}; diff --git a/src/middleware/verificarToken.middleware.js b/src/middleware/verificarToken.middleware.js new file mode 100644 index 0000000..7314698 --- /dev/null +++ b/src/middleware/verificarToken.middleware.js @@ -0,0 +1,43 @@ +import dotenv from "dotenv"; +import jwt from "jsonwebtoken"; + +dotenv.config(); + +export const verificarToken = (request, response, next) => { + const token = request.header("Authorization"); + + if (!token) { + throw { + statusCode: 401, + message: "Acceso no autorizado, token no proporcionado", + code: "TOKEN_NOT_FOUND", + details: + "El token no se mando o no esta autorizado para realizar esta peticion", + }; + } + + const bearerToken = token.split(" ")[1]; + if (!bearerToken) { + throw { + statusCode: 401, + message: "Acceso no autorizado, token no proporcionado", + code: "TOKEN_NOT_FOUND", + details: + "El token no se mando o no esta autorizado para realizar esta peticion", + }; + } + + try { + const secretKey = process.env.JWT_SECRET; + const decoded = jwt.verify(bearerToken, secretKey); + request.usuario = decoded; + next(); + } catch (error) { + throw { + statusCode: 401, + message: "Acceso no autorizado: token inválido", + code: "TOKEN_INVALID", + details: error.message || "El token no pudo ser verificado", + }; + } +}; diff --git a/src/models/users/functions/authGoogle.models.js b/src/models/users/functions/authGoogle.models.js new file mode 100644 index 0000000..8a99947 --- /dev/null +++ b/src/models/users/functions/authGoogle.models.js @@ -0,0 +1,21 @@ +import { connectionQuery } from "../../../helpers/connection.helpers.js"; + +export const checkUser = async (email) => { + const queryCheckUser = `SELECT * FROM users WHERE Email = ?`; + const params = [email]; + return await connectionQuery(queryCheckUser, params); +}; + +export const createUser = async (newUserId, name, email, picture) => { + const queryInsertByGoogle = ` + INSERT INTO users (ID, NameUser, Email, Password, ProfilePicture, AccountType, AccountStatus) + VALUES (?, ?, ?, NULL, ?, 'google', 'Inactivo')`; + const params = [newUserId, name, email, picture]; + return await connectionQuery(queryInsertByGoogle, params); +}; + +export const getRecoveryUserById = async () => { + const queryGetUser = `SELECT ID, NameUser, Email, ProfilePicture, Role, AccountType, AccountStatus FROM users WHERE ID = ?`; + const params = [newUserId]; + return await connectionQuery(queryGetUser, params); +}; diff --git a/src/models/users/functions/deleteUserModel.js b/src/models/users/functions/delete.models.js similarity index 98% rename from src/models/users/functions/deleteUserModel.js rename to src/models/users/functions/delete.models.js index c159c3a..d071f75 100644 --- a/src/models/users/functions/deleteUserModel.js +++ b/src/models/users/functions/delete.models.js @@ -1,4 +1,4 @@ -import { connectionQuery } from "../../../helpers/connection.helper.js"; +import { connectionQuery } from "../../../helpers/connection.helpers.js"; export const validateFoundUserToEliminated = async (userId) => { const query = `SELECT NameUser FROM users WHERE id = ?`; diff --git a/src/models/users/functions/insertUserModel.js b/src/models/users/functions/insert.models.js similarity index 97% rename from src/models/users/functions/insertUserModel.js rename to src/models/users/functions/insert.models.js index 210e090..627d134 100644 --- a/src/models/users/functions/insertUserModel.js +++ b/src/models/users/functions/insert.models.js @@ -1,4 +1,4 @@ -import { connectionQuery } from "../../../helpers/connection.helper.js"; +import { connectionQuery } from "../../../helpers/connection.helpers.js"; export const insertUser = async ( nameUser, diff --git a/src/models/users/functions/listUsersModel.js b/src/models/users/functions/list.models.js similarity index 95% rename from src/models/users/functions/listUsersModel.js rename to src/models/users/functions/list.models.js index 7e280c9..76bfd9b 100644 --- a/src/models/users/functions/listUsersModel.js +++ b/src/models/users/functions/list.models.js @@ -1,4 +1,4 @@ -import { connectionQuery } from "../../../helpers/connection.helper.js"; +import { connectionQuery } from "../../../helpers/connection.helpers.js"; export const listUsersModel = async (query, params) => { const result = await connectionQuery(query, params); diff --git a/src/models/users/functions/registerUserModel.js b/src/models/users/functions/register.models.js similarity index 97% rename from src/models/users/functions/registerUserModel.js rename to src/models/users/functions/register.models.js index a8dafd9..f96f9a9 100644 --- a/src/models/users/functions/registerUserModel.js +++ b/src/models/users/functions/register.models.js @@ -1,4 +1,4 @@ -import { connectionQuery } from "../../../helpers/connection.helper.js"; +import { connectionQuery } from "../../../helpers/connection.helpers.js"; export const registerUser = async (nameUser, email, hashedPassword) => { const query = ` diff --git a/src/models/users/functions/updateUserModel.js b/src/models/users/functions/update.models.js similarity index 98% rename from src/models/users/functions/updateUserModel.js rename to src/models/users/functions/update.models.js index f8954d4..7980eca 100644 --- a/src/models/users/functions/updateUserModel.js +++ b/src/models/users/functions/update.models.js @@ -1,4 +1,4 @@ -import { connectionQuery } from "../../../helpers/connection.helper.js"; +import { connectionQuery } from "../../../helpers/connection.helpers.js"; export const findUserById = async (userId) => { const query = `SELECT * FROM users WHERE ID = ?`; diff --git a/src/models/users/index.js b/src/models/users/index.js index b1d340e..e109b91 100644 --- a/src/models/users/index.js +++ b/src/models/users/index.js @@ -1,6 +1,7 @@ -export * from "./functions/deleteUserModel.js"; -export * from "./functions/insertUserModel.js"; -export * from "./functions/listUsersModel.js"; -export * from "./functions/registerUserModel.js"; -export * from "./functions/updateUserModel.js"; +export * from "./functions/authGoogle.models.js"; +export * from "./functions/delete.models.js"; +export * from "./functions/insert.models.js"; +export * from "./functions/list.models.js"; +export * from "./functions/register.models.js"; +export * from "./functions/update.models.js"; export * from "./index.js"; diff --git a/src/router/googleRoute.js b/src/router/googleRoute.js deleted file mode 100644 index cbc713b..0000000 --- a/src/router/googleRoute.js +++ /dev/null @@ -1,9 +0,0 @@ -import express from "express"; - -import GoogleControllers from "../controllers/googleControllers.js"; - -const apiGoogle = express.Router(); - -apiGoogle.post("/auth/google", GoogleControllers.loginGoogle); - -export { apiGoogle }; diff --git a/src/router/catAssetsRouter.js b/src/routes/catAssetsRouter.js similarity index 99% rename from src/router/catAssetsRouter.js rename to src/routes/catAssetsRouter.js index 3b55a80..5f011a1 100644 --- a/src/router/catAssetsRouter.js +++ b/src/routes/catAssetsRouter.js @@ -1,7 +1,7 @@ import express from "express"; import CatActivosControllers from "../controllers/catAssetsControllers.js"; -import { verificarToken } from "../middleware/verificarToken.js"; +import { verificarToken } from "../middleware/verificarToken.middleware.js"; const apiCatActivos = express.Router(); diff --git a/src/router/catInventarioRouter.js b/src/routes/catInventarioRouter.js similarity index 99% rename from src/router/catInventarioRouter.js rename to src/routes/catInventarioRouter.js index effd9a5..bb24775 100644 --- a/src/router/catInventarioRouter.js +++ b/src/routes/catInventarioRouter.js @@ -1,7 +1,7 @@ import express from "express"; import CatInventarioControllers from "../controllers/catInventarioControllers.js"; -import { verificarToken } from "../middleware/verificarToken.js"; +import { verificarToken } from "../middleware/verificarToken.middleware.js"; const apiCatInventario = express.Router(); diff --git a/src/router/catSuppliesRouter.js b/src/routes/catSuppliesRouter.js similarity index 99% rename from src/router/catSuppliesRouter.js rename to src/routes/catSuppliesRouter.js index 4aafec0..42973bd 100644 --- a/src/router/catSuppliesRouter.js +++ b/src/routes/catSuppliesRouter.js @@ -1,7 +1,7 @@ import express from "express"; import CatInsumosControllers from "../controllers/catSuppliesControllers.js"; -import { verificarToken } from "../middleware/verificarToken.js"; +import { verificarToken } from "../middleware/verificarToken.middleware.js"; const apiCatInsumos = express.Router(); diff --git a/src/routes/google.route.js b/src/routes/google.route.js new file mode 100644 index 0000000..da45f0d --- /dev/null +++ b/src/routes/google.route.js @@ -0,0 +1,23 @@ +import express from "express"; + +import { loginFromGoogle } from "../controllers/users/index.js"; +import { methodCreated, methodOK } from "../server/serverMethods.js"; + +const apiGoogle = express.Router(); + +apiGoogle.post("/auth/google", async (request, response, next) => { + try { + const { credential } = request.body; + + const { responseData, isNewUser } = await loginFromGoogle(credential); + if (isNewUser) { + methodCreated(request, response, responseData); + } else { + methodOK(request, response, responseData); + } + } catch (error) { + next(error); + } +}); + +export { apiGoogle }; diff --git a/src/router/index.js b/src/routes/index.js similarity index 77% rename from src/router/index.js rename to src/routes/index.js index 5827865..4071923 100644 --- a/src/router/index.js +++ b/src/routes/index.js @@ -3,10 +3,11 @@ import express from "express"; import { apiCatActivos } from "./catAssetsRouter.js"; import { apiCatInventario } from "./catInventarioRouter.js"; import { apiCatInsumos } from "./catSuppliesRouter.js"; -import { apiGoogle } from "./googleRoute.js"; +import { apiGoogle } from "./google.route.js"; import { apiMaestros } from "./maestrosRouter.js"; import { apiPadres } from "./padresRouter.js"; import { apiEstudiantes } from "./studentsRouter.js"; +import { apiToken } from "./token.routes.js"; import { apiUsuarios } from "./users.routes.js"; const router = express.Router(); @@ -22,6 +23,7 @@ const router = express.Router(); // apiEstudiantes, // ); -router.use("/api/v1/users", apiUsuarios); +router.use("/api/v1/users", apiUsuarios, apiGoogle); +router.use("/api/v1/token", apiToken); export { router }; diff --git a/src/router/maestrosRouter.js b/src/routes/maestrosRouter.js similarity index 99% rename from src/router/maestrosRouter.js rename to src/routes/maestrosRouter.js index 53cd734..119673d 100644 --- a/src/router/maestrosRouter.js +++ b/src/routes/maestrosRouter.js @@ -1,7 +1,7 @@ import express from "express"; import MaestrosControllers from "../controllers/maestrosControllers.js"; -import { verificarToken } from "../middleware/verificarToken.js"; +import { verificarToken } from "../middleware/verificarToken.middleware.js"; const apiMaestros = express.Router(); diff --git a/src/router/padresRouter.js b/src/routes/padresRouter.js similarity index 99% rename from src/router/padresRouter.js rename to src/routes/padresRouter.js index 7e11c76..0cfb591 100644 --- a/src/router/padresRouter.js +++ b/src/routes/padresRouter.js @@ -1,7 +1,7 @@ import express from "express"; import PadresControllers from "../controllers/padresControllers.js"; -import { verificarToken } from "../middleware/verificarToken.js"; +import { verificarToken } from "../middleware/verificarToken.middleware.js"; const apiPadres = express.Router(); diff --git a/src/router/studentsRouter.js b/src/routes/studentsRouter.js similarity index 78% rename from src/router/studentsRouter.js rename to src/routes/studentsRouter.js index 17e7870..f760864 100644 --- a/src/router/studentsRouter.js +++ b/src/routes/studentsRouter.js @@ -1,7 +1,7 @@ import express from "express"; import EstudiantesControllers from "../controllers/studentsController.js"; -import { verificarToken } from "../middleware/verificarToken.js"; +import { verificarToken } from "../middleware/verificarToken.middleware.js"; const apiEstudiantes = express.Router(); diff --git a/src/routes/token.routes.js b/src/routes/token.routes.js new file mode 100644 index 0000000..81bf9f6 --- /dev/null +++ b/src/routes/token.routes.js @@ -0,0 +1,81 @@ +import express from "express"; + +import { RefreshToken } from "../controllers/token/index.js"; +import { verificarToken } from "../middleware/verificarToken.middleware.js"; +import { methodOK } from "../server/serverMethods.js"; + +const apiToken = express.Router(); + +/** + * @swagger + * /token/refresh: + * post: + * summary: Refresca el token de sesión + * description: Genera un nuevo token de acceso si el token anterior es válido y aún no ha expirado. Se recomienda usar este endpoint desde el frontend cuando el token está por expirar. + * tags: + * - Token + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - token + * properties: + * token: + * type: string + * description: Token de acceso actual que está por expirar. + * example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... + * responses: + * 200: + * description: Token actualizado exitosamente. + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * example: true + * message: + * type: string + * example: Consulta realizada correctamente + * data: + * type: string + * description: Nuevo token generado + * example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... + * metadata: + * type: object + * properties: + * timestamp: + * type: string + * format: date-time + * example: 2025-07-04T08:06:58.280Z + * requestId: + * type: string + * format: uuid + * example: baeea511-fa16-437a-a122-b99a35f76bd8 + * dataCount: + * type: string + * example: "1" + * 403: + * description: El usuario está inactivo. Por favor contacte al administrador. + * 500: + * description: Error interno del servidor. + */ + +//POST /api/token/refresh +apiToken.post("/refresh", verificarToken, async (request, response, next) => { + try { + const { token } = request.body; + console.log(token); + const newToken = await RefreshToken(token); + console.log(newToken); + methodOK(request, response, newToken); + } catch (error) { + next(error); + } +}); + +export { apiToken }; diff --git a/src/router/users.routes.js b/src/routes/users.routes.js similarity index 99% rename from src/router/users.routes.js rename to src/routes/users.routes.js index 23fb92f..de920da 100644 --- a/src/router/users.routes.js +++ b/src/routes/users.routes.js @@ -21,7 +21,7 @@ import { searchUsersRateLimiter, updateUserRateLimiter, } from "../helpers/usersHelpers/rateLimitRequestUsers.js"; -import { verificarToken } from "../middleware/verificarToken.js"; +import { verificarToken } from "../middleware/verificarToken.middleware.js"; import { methodCreated, methodOK } from "../server/serverMethods.js"; const apiUsuarios = express.Router(); diff --git a/src/services/users/functions/authService.js b/src/services/users/functions/auth.services.js similarity index 91% rename from src/services/users/functions/authService.js rename to src/services/users/functions/auth.services.js index 9f8889d..424d5c0 100644 --- a/src/services/users/functions/authService.js +++ b/src/services/users/functions/auth.services.js @@ -1,8 +1,8 @@ import hashedArg from "argon2"; -import { findUserByEmail } from "../../../helpers/findUserByEmail.js"; -import { createToken } from "../../../helpers/jwt.js"; -import { lastLogin } from "../../../helpers/userLastLogin.js"; +import { findUserByEmail } from "../../../helpers/findUserByEmail.helpers.js"; +import { createToken } from "../../../helpers/jwt.helpers.js"; +import { lastLogin } from "../../../helpers/userLastLogin.helpers.js"; export const loginService = async ({ email, password }) => { const user = await findUserByEmail(email); diff --git a/src/services/users/functions/authGoogle.services.js b/src/services/users/functions/authGoogle.services.js new file mode 100644 index 0000000..33d5ff6 --- /dev/null +++ b/src/services/users/functions/authGoogle.services.js @@ -0,0 +1,127 @@ +import dotenv from "dotenv"; +import crypto from "node:crypto"; + +import { createTokenGoogle } from "../../../helpers/jwtGoogle.helpers.js"; +import { lastLogin } from "../../../helpers/userLastLogin.helpers.js"; +import { googleClient } from "../../../lib/clientGoogle.lib.js"; +import { + checkUser, + createUser, + getRecoveryUserById, +} from "../../../models/users/index.js"; + +dotenv.config(); + +export const loginGoogleService = async (credential) => { + if (!credential) { + throw { + statusCode: 400, + message: "ID Token no proporcionado", + code: "FIELDS_REQUIRED", + details: "Todos los campos son obligatorios para loguarse con Google", + }; + } + + const ticket = await googleClient.verifyIdToken({ + idToken: credential, + audience: process.env.CLIENT_ID_GOOGLE, + }); + + const payload = ticket.getPayload(); + + // Extraer datos del payload verificado + const googleId = payload["sub"]; + const email = payload["email"]; + const name = payload["name"]; + const picture = payload["picture"]; + + if (!googleId || !email) { + // El token es válido pero falta info crucial, caso raro pero posible + throw { + statusCode: 400, + message: "Información esencial del usuario de Google no encontrada", + code: "GOOGLE_USER_INFO_MISSING", + details: "El token de Google es valido pero falta informacion escencial", + }; + } + + const queryCheckUser = await checkUser(email); + + // Variable para almacenar el usuario encontrado o creado + let user = null; + let isNewUser = false; + + if (queryCheckUser.length > 0) { + user = queryCheckUser[0]; + + // Si el usuario ya existe con método local ('normal'), no puede ingresar con Google + if (user.AccountType === "normal") { + throw { + statusCode: 403, + message: "No se puede iniciar sesion con Google", + code: "GOOGLE_LOGIN_FORBIDDEN", + details: + "Este correo ya se encuentra registrado por email y contraseña normal", + }; + } + } else { + // Si el usuario NO existe en la base de datos, lo creamos + isNewUser = true; + const newUserId = crypto.randomUUID(); + + const queryCreateUser = await createUser(newUserId, name, email, picture); + + if (queryCreateUser.affectedRows === 0) { + // Esto no debería pasar si no hubo error, pero es una verificación de seguridad + throw { + statusCode: 500, + message: "Error al crear el usuario", + code: "USER_CREATION_ERROR", + details: "No se pudo crear el usuario con los datos proporcionados", + }; + } + + // Recuperar los datos completos del usuario recién creado (incluyendo Role por defecto, etc.) + const queryGetUser = await getRecoveryUserById(newUserId); + if (queryGetUser.length === 0) { + // Error crítico si no se encuentra el usuario recién insertado + throw { + statusCode: 500, + message: "Error interno al recuperar el usuario recien creado", + code: "USER_RECOVERY_ERROR", + details: "No se pudo recuperar el usuario recien creado", + }; + } + user = newUserRows[0]; + } + + if (user.AccountStatus === "Inactivo") { + throw { + statusCode: 403, + message: "Cuenta inactiva", + code: "ACCOUNT_INACTIVE", + details: "Tu cuenta esta inactiva. Porfavor, contacta al administrador.", + }; + } + + // Generar el token de tu aplicación (JWT) para el usuario identificado/creado + const appToken = createTokenGoogle(user); + // Actualizar la fecha de último login + await lastLogin(user.ID); + // Enviar respuesta exitosa al frontend + return { + responseData: { + token: appToken, + user: { + ID: user.ID, + NameUser: user.NameUser, + Email: user.Email, + ProfilePicture: user.ProfilePicture, + Role: user.Role, + AccountType: user.AccountType, + AccountStatus: user.AccountStatus, + }, + }, + isNewUser, + }; +}; diff --git a/src/services/users/functions/deleteUserService.js b/src/services/users/functions/delete.services.js similarity index 100% rename from src/services/users/functions/deleteUserService.js rename to src/services/users/functions/delete.services.js diff --git a/src/services/users/functions/insertUserService.js b/src/services/users/functions/insert.services.js similarity index 98% rename from src/services/users/functions/insertUserService.js rename to src/services/users/functions/insert.services.js index e1d3172..6d29217 100644 --- a/src/services/users/functions/insertUserService.js +++ b/src/services/users/functions/insert.services.js @@ -1,8 +1,8 @@ import { faker } from "@faker-js/faker"; import hashedArg from "argon2"; -import { findUserByEmail } from "../../../helpers/findUserByEmail.js"; -import { getUserByEmail } from "../../../helpers/getUserByEmail.js"; +import { findUserByEmail } from "../../../helpers/findUserByEmail.helpers.js"; +import { getUserByEmail } from "../../../helpers/getUserByEmail.helpers.js"; import { insertUser } from "../../../models/users/index.js"; export const insertUserService = async ({ diff --git a/src/services/users/functions/listUsersService.js b/src/services/users/functions/list.services.js similarity index 100% rename from src/services/users/functions/listUsersService.js rename to src/services/users/functions/list.services.js diff --git a/src/services/users/functions/registerUserService.js b/src/services/users/functions/register.services.js similarity index 96% rename from src/services/users/functions/registerUserService.js rename to src/services/users/functions/register.services.js index 39a657b..8a27901 100644 --- a/src/services/users/functions/registerUserService.js +++ b/src/services/users/functions/register.services.js @@ -1,9 +1,9 @@ import { de } from "@faker-js/faker"; import hashedArg from "argon2"; -import { findUserByEmail } from "../../../helpers/findUserByEmail.js"; -import { getUserByEmail } from "../../../helpers/getUserByEmail.js"; -import { registerUser } from "../../../models/users/functions/registerUserModel.js"; +import { findUserByEmail } from "../../../helpers/findUserByEmail.helpers.js"; +import { getUserByEmail } from "../../../helpers/getUserByEmail.helpers.js"; +import { registerUser } from "../../../models/users/functions/register.models.js"; export const registerUserService = async ({ nameUser, email, password }) => { if (!nameUser || !email || !password) { diff --git a/src/services/users/functions/searchUserService.js b/src/services/users/functions/search.services.js similarity index 98% rename from src/services/users/functions/searchUserService.js rename to src/services/users/functions/search.services.js index 9710f25..6422ca7 100644 --- a/src/services/users/functions/searchUserService.js +++ b/src/services/users/functions/search.services.js @@ -1,4 +1,4 @@ -import { connectionQuery } from "../../../helpers/connection.helper.js"; +import { connectionQuery } from "../../../helpers/connection.helpers.js"; export const searchUserService = async (email) => { let querySearchUsers = `SELECT * FROM users WHERE 1=1`; diff --git a/src/services/users/functions/updateUserService.js b/src/services/users/functions/update.services.js similarity index 98% rename from src/services/users/functions/updateUserService.js rename to src/services/users/functions/update.services.js index 806f2a2..d2c25e5 100644 --- a/src/services/users/functions/updateUserService.js +++ b/src/services/users/functions/update.services.js @@ -1,6 +1,6 @@ import hashedArg from "argon2"; -import { findEmailInOtherUser } from "../../../helpers/getUserByEmailAndId.js"; +import { findEmailInOtherUser } from "../../../helpers/getUserByEmailAndId.helpers.js"; import { findUserById, updateUserWithPassword, diff --git a/src/services/users/index.js b/src/services/users/index.js index 4785242..024930d 100644 --- a/src/services/users/index.js +++ b/src/services/users/index.js @@ -1,8 +1,9 @@ -export * from "./functions/authService.js"; -export * from "./functions/deleteUserService.js"; -export * from "./functions/insertUserService.js"; -export * from "./functions/listUsersService.js"; -export * from "./functions/registerUserService.js"; -export * from "./functions/searchUserService.js"; -export * from "./functions/updateUserService.js"; +export * from "./functions/auth.services.js"; +export * from "./functions/authGoogle.services.js"; +export * from "./functions/delete.services.js"; +export * from "./functions/insert.services.js"; +export * from "./functions/list.services.js"; +export * from "./functions/register.services.js"; +export * from "./functions/search.services.js"; +export * from "./functions/update.services.js"; export * from "./index.js"; diff --git a/yarn.lock b/yarn.lock index 8c03867..1a961ca 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1138,7 +1138,7 @@ ansi-styles@^5.0.0: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== -anymatch@^3.0.3: +anymatch@^3.0.3, anymatch@~3.1.2: version "3.1.3" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== @@ -1370,6 +1370,11 @@ bignumber.js@^9.0.0: resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.3.0.tgz#bdba7e2a4c1a2eba08290e8dcad4f36393c92acd" integrity sha512-EM7aMFTXbptt/wZdMlBv2t8IViwQL+h6SLHosp8Yf0dqJMTnY6iL32opnAB6kAdL0SZPuvcAzFr31o0c/R3/RA== +binary-extensions@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" + integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== + body-parser@1.20.3: version "1.20.3" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.3.tgz#1953431221c6fb5cd63c4b36d53fab0928e548c6" @@ -1411,7 +1416,7 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -braces@^3.0.3: +braces@^3.0.3, braces@~3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== @@ -1508,7 +1513,7 @@ caniuse-lite@^1.0.30001716: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001717.tgz#5d9fec5ce09796a1893013825510678928aca129" integrity sha512-auPpttCq6BDEG8ZAuHJIplGw6GODhjw+/11e7IjpnYCxZcW/ONgPs0KVBJ0d1bY3e2+7PRe5RCLyP+PfwVgkYw== -chalk@^4.0.0: +chalk@^4.0.0, chalk@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -1526,6 +1531,21 @@ chardet@^0.7.0: resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== +chokidar@^3.5.2: + version "3.6.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" + integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + ci-info@^3.2.0: version "3.9.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" @@ -1594,6 +1614,19 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== +concurrently@^9.2.0: + version "9.2.0" + resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-9.2.0.tgz#233e3892ceb0b5db9fd49e9c8c739737a7b638b5" + integrity sha512-IsB/fiXTupmagMW4MNp2lx2cdSN2FfZq78vF90LBB+zZHArbIQZjQtzXCiXnvTxCZSvXanTqFLWBjw2UkLx1SQ== + dependencies: + chalk "^4.1.2" + lodash "^4.17.21" + rxjs "^7.8.1" + shell-quote "^1.8.1" + supports-color "^8.1.1" + tree-kill "^1.2.2" + yargs "^17.7.2" + content-disposition@0.5.4: version "0.5.4" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" @@ -1716,6 +1749,13 @@ debug@^3.2.7: dependencies: ms "^2.1.1" +debug@^4: + version "4.4.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.1.tgz#e5a8bc6cbc4c6cd3e64308b0693a3d4fa550189b" + integrity sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ== + dependencies: + ms "^2.1.3" + dedent@^1.0.0: version "1.6.0" resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.6.0.tgz#79d52d6389b1ffa67d2bcef59ba51847a9d503b2" @@ -2595,7 +2635,7 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== -fsevents@^2.3.2: +fsevents@^2.3.2, fsevents@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== @@ -2714,6 +2754,13 @@ glob-parent@^6.0.2: dependencies: is-glob "^4.0.3" +glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + glob@7.1.6: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" @@ -2813,6 +2860,11 @@ has-bigints@^1.0.2: resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.1.0.tgz#28607e965ac967e03cd2a2c70a2636a1edad49fe" integrity sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg== +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + has-flag@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" @@ -2914,6 +2966,11 @@ iconv-lite@0.6.3, iconv-lite@^0.6.2, iconv-lite@^0.6.3: dependencies: safer-buffer ">= 2.1.2 < 3.0.0" +ignore-by-default@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" + integrity sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA== + ignore@^5.1.1, ignore@^5.2.0: version "5.3.2" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" @@ -3012,6 +3069,13 @@ is-bigint@^1.1.0: dependencies: has-bigints "^1.0.2" +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + is-boolean-object@^1.2.1: version "1.2.2" resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.2.2.tgz#7067f47709809a393c71ff5bb3e135d8a9215d9e" @@ -3081,7 +3145,7 @@ is-generator-function@^1.0.10: has-tostringtag "^1.0.2" safe-regex-test "^1.1.0" -is-glob@^4.0.0, is-glob@^4.0.3: +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== @@ -4130,7 +4194,23 @@ node-releases@^2.0.19: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314" integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw== -normalize-path@^3.0.0: +nodemon@^3.1.10: + version "3.1.10" + resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-3.1.10.tgz#5015c5eb4fffcb24d98cf9454df14f4fecec9bc1" + integrity sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw== + dependencies: + chokidar "^3.5.2" + debug "^4" + ignore-by-default "^1.0.1" + minimatch "^3.1.2" + pstree.remy "^1.1.8" + semver "^7.5.3" + simple-update-notifier "^2.0.0" + supports-color "^5.5.0" + touch "^3.1.0" + undefsafe "^2.0.5" + +normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== @@ -4377,7 +4457,7 @@ picocolors@^1.1.1: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== -picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1: +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== @@ -4461,6 +4541,11 @@ proxy-addr@^2.0.7, proxy-addr@~2.0.7: forwarded "0.2.0" ipaddr.js "1.9.1" +pstree.remy@^1.1.8: + version "1.1.8" + resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a" + integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w== + punycode@2.x.x, punycode@^2.1.0: version "2.3.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" @@ -4525,6 +4610,13 @@ react-is@^18.0.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + reflect.getprototypeof@^1.0.6, reflect.getprototypeof@^1.0.9: version "1.0.10" resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz#c629219e78a3316d8b604c765ef68996964e7bf9" @@ -4636,7 +4728,7 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" -rxjs@^7.8.2: +rxjs@^7.8.1, rxjs@^7.8.2: version "7.8.2" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.2.tgz#955bc473ed8af11a002a2be52071bf475638607b" integrity sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA== @@ -4810,6 +4902,11 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== +shell-quote@^1.8.1: + version "1.8.3" + resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.3.tgz#55e40ef33cf5c689902353a3d8cd1a6725f08b4b" + integrity sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw== + side-channel-list@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad" @@ -4860,6 +4957,13 @@ signal-exit@^4.1.0: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== +simple-update-notifier@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz#d70b92bdab7d6d90dfd73931195a30b6e3d7cebb" + integrity sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w== + dependencies: + semver "^7.5.3" + sisteransi@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" @@ -5046,6 +5150,13 @@ strip-json-comments@^3.1.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== +supports-color@^5.5.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" @@ -5053,7 +5164,7 @@ supports-color@^7.1.0: dependencies: has-flag "^4.0.0" -supports-color@^8.0.0: +supports-color@^8.0.0, supports-color@^8.1.1: version "8.1.1" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== @@ -5153,11 +5264,21 @@ topo@3.x.x: dependencies: hoek "6.x.x" +touch@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.1.tgz#097a23d7b161476435e5c1344a95c0f75b4a5694" + integrity sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA== + tr46@~0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== +tree-kill@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" + integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== + tsconfig-paths@^3.15.0: version "3.15.0" resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz#5299ec605e55b1abb23ec939ef15edaf483070d4" @@ -5272,6 +5393,11 @@ unbox-primitive@^1.1.0: has-symbols "^1.1.0" which-boxed-primitive "^1.1.1" +undefsafe@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" + integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== + undici-types@~6.21.0: version "6.21.0" resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" @@ -5472,7 +5598,7 @@ yargs-parser@^21.1.1: resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== -yargs@^17.3.1: +yargs@^17.3.1, yargs@^17.7.2: version "17.7.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==