From 46341a18b8f5e819f0820911c45987591c54beea Mon Sep 17 00:00:00 2001 From: Matias Bais Date: Mon, 4 Mar 2024 18:06:44 -0300 Subject: [PATCH] =?UTF-8?q?busqueda=20en=20admin=20usuarios,=20cambiar=20c?= =?UTF-8?q?ontrase=C3=B1a=20y=20fotos=20de=20perfil?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 4 +- api/v1/router.js | 61 ++++++++++++++++-- frontend/router.js | 4 +- frontend/static/componentes/chipusuario.js | 4 +- frontend/static/componentes/formulario.js | 46 ++++++++----- .../pantallas/administracion-usuarios.js | 6 +- .../static/pantallas/perfil-propio-info.js | 42 +++++++++++- .../static/scripts/administracion-usuarios.js | 4 +- package.json | 1 + storage/img/user.webp | Bin 0 -> 4048 bytes 10 files changed, 138 insertions(+), 34 deletions(-) create mode 100644 storage/img/user.webp diff --git a/.gitignore b/.gitignore index dde6bf2d..30411db2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ node_modules package-lock.json .vscode -.personal \ No newline at end of file +.personal +/storage/img/* +!/storage/img/user.webp \ No newline at end of file diff --git a/api/v1/router.js b/api/v1/router.js index 3ae31906..aaf6c67f 100644 --- a/api/v1/router.js +++ b/api/v1/router.js @@ -1,5 +1,26 @@ import * as express from "express"; import * as bcrypt from "bcrypt"; +import multer from "multer"; +import path from "path" +const storage = multer.diskStorage({ + destination: function (req, file, cb) { + cb(null, './storage/img') + }, + filename: function (req, file, cb) { + cb(null, "imagenPerfil-" + req.session.usuario.DNI+".jpg") + }, + +}) +var upload = multer({storage:storage, + fileFilter: function(req, file, cb){ + const allowedExtensions = ['.jpg', '.png']; // Add more extensions as needed + const fileExtension = path.extname(file.originalname).toLowerCase(); + if (allowedExtensions.includes(fileExtension)) { + cb(null, true); // Accept the file + } else { + cb(null, false); // Reject the file + } + }}) const router = express.Router(); import { Usuario, @@ -84,6 +105,14 @@ router.delete("/sesion", function (req, res) { // usuario +router.get("/usuario/:DNI/foto", function(req, res){ + res.sendFile("imagenPerfil-"+req.params.DNI+".jpg", {'root': './storage/img'}, (err)=>{ + if (err) { + res.sendFile("user.webp", {'root': './storage/img'}) + } + }); +}); + router.get("/usuario", function (req, res) { if (!req.session.usuario) { res.status(401).send("No se posee sesión válida activa"); @@ -164,6 +193,16 @@ router.get("/usuario", function (req, res) { where.nombre = { [Sequelize.Op.substring]: filtro }; include.push(Carrera); where["$carrera.legajo$"] = { [Sequelize.Op.substring]: filtro }; + }else if( req.query.searchInput){ + + where = { + [Sequelize.Op.or]: [ + { DNI: { [Sequelize.Op.substring]: req.query.searchInput } }, + { nombre: { [Sequelize.Op.substring]: req.query.searchInput } }, + { '$perfil.nombre$': { [Sequelize.Op.startsWith]: req.query.searchInput } } + ] + }; + opciones.where = where; } if (include.length) { @@ -419,18 +458,28 @@ router.delete("/usuario/:DNI/bloqueo", function (req, res) { }); }); -router.patch("/usuario", function (req, res) { +router.patch("/usuario", upload.single("image"), function (req, res) { + //req.file tiene la imagen if (!req.session.usuario) { res.status(401).send("Usuario no tiene sesión válida activa"); return; } + + if(!req.file){ + //req.file solo existe si la imagen cumple con los formatos de arriba + //TODO esto no funcionaría si solo manda la contraseña + res.status(400).send("Petición mal formada") + } Usuario.findByPk(req.session.usuario.DNI) .then((usuario) => { - //TODO Feature: definir que mas puede cambiar y que constituye datos inválidos - usuario.contrasenia = req.body.contrasenia; - usuario.save(); - res.status(200).send("Datos actualizados exitosamente"); - }) + if(bcrypt.compare(req.body.contraseniaAnterior, usuario.contrasenia)){ + usuario.contrasenia = req.body.contraseniaNueva; + usuario.save(); + res.status(200).send("Datos actualizados exitosamente"); + return; + } + res.status(402).send("Contraseña anterior no válida") + }) .catch((err) => { res.status(500).send(err); }); diff --git a/frontend/router.js b/frontend/router.js index 279166df..79e7fd34 100644 --- a/frontend/router.js +++ b/frontend/router.js @@ -544,8 +544,8 @@ router.get("/administracion/usuarios", async (req, res) => { res.send(pagina.render()); return; } - - let pagina = PantallaAdministracionUsuarios(req.path, req.session); + const query = req.query.searchInput; + let pagina = PantallaAdministracionUsuarios(req.path, req.session, query); res.send(pagina.render()); }); // Ruta Para búsqueda diff --git a/frontend/static/componentes/chipusuario.js b/frontend/static/componentes/chipusuario.js index f7e555ad..fc7cfeed 100644 --- a/frontend/static/componentes/chipusuario.js +++ b/frontend/static/componentes/chipusuario.js @@ -28,7 +28,7 @@ class ChipUsuario { if (this.#esPerfil) { return `
- +
DNI: ${this.#DNI}
Nombre: ${this.#nombreusuario}
@@ -43,7 +43,7 @@ class ChipUsuario { } else { return `
- + ${ this.#nombreusuario } diff --git a/frontend/static/componentes/formulario.js b/frontend/static/componentes/formulario.js index b4cd2675..cd0ed569 100644 --- a/frontend/static/componentes/formulario.js +++ b/frontend/static/componentes/formulario.js @@ -33,25 +33,34 @@ class Formulario{ this.#alEnviar(); } + // Crear un objeto FormData para facilitar la obtención de datos del formulario const formData = new FormData(e.target); + + const fileInput = document.querySelector(`#${this.#id} input[type="file"]`); + let datos; + if(fileInput){ + datos = formData; + - // Crear un objeto para almacenar los datos - const datos = {}; - - // Iterar sobre las entradas de FormData y asignarlas al objeto de datos - formData.forEach((value, key) => { - // Reflect.has in favor of: object.hasOwnProperty(key) - if(!Reflect.has(datos, key)){ - datos[key] = value; - return; - } - if(!Array.isArray(datos[key])){ - datos[key] = [datos[key]]; - } - datos[key].push(value); - }); + }else{ + // Crear un objeto para almacenar los datos + datos = {}; + + // Iterar sobre las entradas de FormData y asignarlas al objeto de datos + formData.forEach((value, key) => { + // Reflect.has in favor of: object.hasOwnProperty(key) + if(!Reflect.has(datos, key)){ + datos[key] = value; + return; + } + if(!Array.isArray(datos[key])){ + datos[key] = [datos[key]]; + } + datos[key].push(value); + }); + } let ok,codigo; superFetch(this.#endpoint,datos,{ method: this.verbo}) .then(res=>{ @@ -63,7 +72,7 @@ class Formulario{ } render(){ - return `
` + return `` + this.campos.reduce((html,c)=>html+(new Campo(c)).render(),'') // TODO Refactor: new Boton ?? +`` @@ -148,6 +157,8 @@ class Campo{ html=html.replace(''; } diff --git a/frontend/static/pantallas/administracion-usuarios.js b/frontend/static/pantallas/administracion-usuarios.js index 125d367a..2e569ef4 100644 --- a/frontend/static/pantallas/administracion-usuarios.js +++ b/frontend/static/pantallas/administracion-usuarios.js @@ -7,10 +7,11 @@ import { Fecha, ChipUsuario, Modal, + Busqueda } from "../componentes/todos.js"; - function crearPantalla(ruta, sesion) { - let tabla = new Tabla("administrar-usuarios", "/api/usuario", [ + function crearPantalla(ruta, sesion, query="") { + let tabla = new Tabla("administrar-usuarios", "/api/usuario?searchInput="+query, [ { nombre: "DNI", celda: (usuario) => @@ -47,6 +48,7 @@ import { sesion: sesion, partes: [ new Modal("Eliminar Usuario", "modal-eliminar-usuario"), + new Busqueda(), tabla, new Boton({ titulo: "Agregar", diff --git a/frontend/static/pantallas/perfil-propio-info.js b/frontend/static/pantallas/perfil-propio-info.js index 4d102bd7..9eba1247 100644 --- a/frontend/static/pantallas/perfil-propio-info.js +++ b/frontend/static/pantallas/perfil-propio-info.js @@ -1,15 +1,53 @@ -import { ChipUsuario, Modal, Pagina } from '../componentes/todos.js' +import { ChipUsuario, Formulario, Modal, Pagina } from '../componentes/todos.js' function crearPagina(ruta, sesion){ let titulo = 'Mi perfil' let modal = new Modal('General','modal-general'); + let form = new Formulario( + "administracion-perfil-editar", + `/api/usuario`, + [ + { + name: "contraseniaNueva", + textoEtiqueta: "Contraseña nueva:", + type: "password", + }, + { + name: "contraseniaAnterior", + textoEtiqueta: "Contraseña anterior:", + type: "password", + }, + { + name: "image", + textoEtiqueta: "Imagen de Perfil: ", + type: "file", + extra: 'accept="image/jpeg, image/png' + }, + ], + (txt, info) => { + if (info.ok) { + + window.location.reload(); + + } else { + // TODO UX: Mejores alertas + alert(`Error ${info.codigo}: ${txt}`); + } + }, + { + verbo: "PATCH", + textoEnviar: "Editar usuario", + clasesBoton: "is-link is-rounded mt-3", + } + ); return new Pagina({ ruta:ruta, titulo: titulo, sesion:sesion, partes:[ modal, - new ChipUsuario(sesion.usuario, true) + new ChipUsuario(sesion.usuario, true), + form ] }); diff --git a/frontend/static/scripts/administracion-usuarios.js b/frontend/static/scripts/administracion-usuarios.js index fd003293..8a0dc551 100644 --- a/frontend/static/scripts/administracion-usuarios.js +++ b/frontend/static/scripts/administracion-usuarios.js @@ -5,9 +5,9 @@ import { Modal } from "../componentes/todos.js"; let pagina = PantallaAdministracionUsuarios(location.pathname, { usuario: window.usuarioActual, -}); +}, location.search.split('=')[1]); let modal = pagina.partes[0]; -let tabla = pagina.partes[1]; +let tabla = pagina.partes[2]; tabla /* ! Tabla */ .iniciar(); diff --git a/package.json b/package.json index 7ee30624..ea8a7b16 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "express": "4.18.2", "express-session": "1.17.3", "mocha": "^10.2.0", + "multer": "^1.4.5-lts.1", "mysql2": "3.6.5", "openai": "4.0.0", "request": "2.88.2", diff --git a/storage/img/user.webp b/storage/img/user.webp new file mode 100644 index 0000000000000000000000000000000000000000..b95a29b66f4658a6acca5dd5ae7db04967651f9e GIT binary patch literal 4048 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 literal 0 HcmV?d00001