https://github.com/waldohidalgo/skate_park_app
Repositorio con el código solución completo de la prueba con la que se finaliza el módulo 8 Implementación de API backend Node Express
https://github.com/waldohidalgo/skate_park_app
bootstrap-icons bootstrap5 desafiolatam express express-fileupload express-handlebars jsonwebtoken pg postgresql uuid
Last synced: 3 months ago
JSON representation
Repositorio con el código solución completo de la prueba con la que se finaliza el módulo 8 Implementación de API backend Node Express
- Host: GitHub
- URL: https://github.com/waldohidalgo/skate_park_app
- Owner: waldohidalgo
- Created: 2024-04-27T01:29:44.000Z (about 2 years ago)
- Default Branch: main
- Last Pushed: 2024-05-06T16:00:02.000Z (about 2 years ago)
- Last Synced: 2025-03-21T15:05:14.884Z (over 1 year ago)
- Topics: bootstrap-icons, bootstrap5, desafiolatam, express, express-fileupload, express-handlebars, jsonwebtoken, pg, postgresql, uuid
- Language: JavaScript
- Homepage: https://skate-park-app.onrender.com/
- Size: 1.27 MB
- Stars: 1
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Solución Completa de la Prueba - Skate Park
Repositorio con el código solución de la prueba con la que se finaliza el módulo 8 **Implementación de API backend Node Express** de la beca dada por Talento Digital para Chile **Desarrollo de aplicaciones Full Stack Javascript Trainee** y dictada por Desafío Latam.
Incorporo bcrypt para el almacenamiento encriptado de passwords en la base de datos
## Tabla de Contenido
- [Solución Completa de la Prueba - Skate Park](#solución-completa-de-la-prueba---skate-park)
- [Tabla de Contenido](#tabla-de-contenido)
- [Librerías utilizadas](#librerías-utilizadas)
- [Deploy](#deploy)
- [Requisitos](#requisitos)
- [Diagrama de Flujo](#diagrama-de-flujo)
- [1.Página Home](#1página-home)
- [2.Página Registro](#2página-registro)
- [2.1.Registro de Participante Exitoso](#21registro-de-participante-exitoso)
- [2.2.Verificación de Registro exitoso de Participante Exitoso Front End](#22verificación-de-registro-exitoso-de-participante-exitoso-front-end)
- [2.3.Verificación de Registro exitoso de Participante Exitoso Back End](#23verificación-de-registro-exitoso-de-participante-exitoso-back-end)
- [3.Página LogIn de Participante](#3página-login-de-participante)
- [3.1.LogIn exitoso de Participante](#31login-exitoso-de-participante)
- [3.2.Página Cuenta de Usuario](#32página-cuenta-de-usuario)
- [3.3.Token Expirado después de 2 minutos](#33token-expirado-después-de-2-minutos)
- [3.4.Alerta Edición exitosa](#34alerta-edición-exitosa)
- [3.5.Verificación edición exitosa front end](#35verificación-edición-exitosa-front-end)
- [3.6.Verificación edición exitosa back end](#36verificación-edición-exitosa-back-end)
- [4.Página Admin](#4página-admin)
- [4.1.Alerta Cambio de estado exitoso de participante por Admin](#41alerta-cambio-de-estado-exitoso-de-participante-por-admin)
- [4.2.Verificación Cambio de estado exitoso de participante por Admin Front End](#42verificación-cambio-de-estado-exitoso-de-participante-por-admin-front-end)
- [4.3.Verificación Cambio de estado exitoso de participante por Admin Back End](#43verificación-cambio-de-estado-exitoso-de-participante-por-admin-back-end)
- [5.Alerta de Eliminación de Participante exitosa](#5alerta-de-eliminación-de-participante-exitosa)
- [5.1.Verificación de Eliminación de participante Front End](#51verificación-de-eliminación-de-participante-front-end)
- [5.2.Verificación de Eliminación de participante Back End](#52verificación-de-eliminación-de-participante-back-end)
- [6.Alerta Archivo Muy Pesado](#6alerta-archivo-muy-pesado)
- [7.Alerta Tipo de Archivo No Permitido](#7alerta-tipo-de-archivo-no-permitido)
- [8.Alerta password y password repetida no coinciden](#8alerta-password-y-password-repetida-no-coinciden)
- [9.Página 404](#9página-404)
- [Soluciones](#soluciones)
- [1. Crear una API REST con el Framework Express (3 Puntos)](#1-crear-una-api-rest-con-el-framework-express-3-puntos)
- [2. Servir contenido dinámico con express-handlebars (3 Puntos)](#2-servir-contenido-dinámico-con-express-handlebars-3-puntos)
- [3. Ofrecer la funcionalidad Upload File con express-fileupload (2 Puntos)](#3-ofrecer-la-funcionalidad-upload-file-con-express-fileupload-2-puntos)
- [4. Implementar seguridad y restricción de recursos o contenido con JWT (2 Puntos)](#4-implementar-seguridad-y-restricción-de-recursos-o-contenido-con-jwt-2-puntos)
- [Extra](#extra)
- [1. Ruta para resetear la data en la base de datos y borrar las imagenes](#1-ruta-para-resetear-la-data-en-la-base-de-datos-y-borrar-las-imagenes)
- [2. Script para resetear la data cada 30 minutos](#2-script-para-resetear-la-data-cada-30-minutos)
## Librerías utilizadas
| Librerias Utilizadas |
| -------------------- |
| bcrypt |
| bootstrap-icons |
| express |
| express-fileupload |
| express-handlebars |
| jsonwebtoken |
| pg |
| uuid |
## Deploy
El proyecto es 100% funcional y esta operativo en la web. Lo he desplegado en Render en el siguiente [link](https://skate-park-app.onrender.com/)
## Requisitos



## Diagrama de Flujo
### 1.Página Home

### 2.Página Registro

#### 2.1.Registro de Participante Exitoso

#### 2.2.Verificación de Registro exitoso de Participante Exitoso Front End

#### 2.3.Verificación de Registro exitoso de Participante Exitoso Back End

### 3.Página LogIn de Participante

#### 3.1.LogIn exitoso de Participante

#### 3.2.Página Cuenta de Usuario

#### 3.3.Token Expirado después de 2 minutos

#### 3.4.Alerta Edición exitosa

#### 3.5.Verificación edición exitosa front end

#### 3.6.Verificación edición exitosa back end

### 4.Página Admin

#### 4.1.Alerta Cambio de estado exitoso de participante por Admin

#### 4.2.Verificación Cambio de estado exitoso de participante por Admin Front End

#### 4.3.Verificación Cambio de estado exitoso de participante por Admin Back End

### 5.Alerta de Eliminación de Participante exitosa

#### 5.1.Verificación de Eliminación de participante Front End

#### 5.2.Verificación de Eliminación de participante Back End

### 6.Alerta Archivo Muy Pesado

### 7.Alerta Tipo de Archivo No Permitido

### 8.Alerta password y password repetida no coinciden

### 9.Página 404

## Soluciones
### 1. Crear una API REST con el Framework Express (3 Puntos)
Para manipular la creación, edición, lectura y eliminación de participantes he creado la siguiente API REST:
```js
router.get("/participantes", getParticipantes);
router.post("/participante", postParticipante);
router.delete("/participante", deleteParticipante);
router.put("/participante", putEditParticipante);
router.patch("/participante", patchEditParticipante);
```
### 2. Servir contenido dinámico con express-handlebars (3 Puntos)
He servido contenido dinámico con express-handlebars por ejemplo la página de Admin para la cual he utilizado el siguiente código:
```hbs
{{> header}}
Administración
#
Foto
Nombre
Años de experiencia
Especialidad
Estado
{{#if ( arrayVacio participantes) }}
No hay participantes
{{else}}
{{#each participantes}}
{{addOne @index}}
{{ this.nombre }}
{{ this.anos_experiencia }}
{{ this.especialidad }}
{{#if this.estado }}
{{else}}
{{/if}}
{{/each}}
{{/if}}
{{> footer}}
```
### 3. Ofrecer la funcionalidad Upload File con express-fileupload (2 Puntos)
Implemento la librería express-fileupload al cargar un archivo en la creación de un participante:
```js
const maxSize = 1 * 1024 * 1024;
export default async function postParticipante(req, res) {
try {
const {
email,
nombre,
anos_experiencia,
especialidad,
password,
password2,
} = req.body;
const participantes = await getAllDataParticipantesQuery();
if (participantes.find((participante) => participante.email === email)) {
res.status(400).send("El email ya existe");
return;
}
const {
foto: { size, mimetype, mv: moveFile },
} = req.files;
if (password !== password2) {
res.status(400).send("Las contraseñas no coinciden");
return;
}
if (size > maxSize) {
res.status(413).send("El tamaño del archivo es demasiado grande");
return;
}
if (
mimetype !== "image/jpeg" &&
mimetype !== "image/png" &&
mimetype !== "image/jpg" &&
mimetype !== "image/gif" &&
mimetype !== "image/webp"
) {
res.status(415).send("El formato del archivo no es válido");
return;
}
const nombreFoto = `${uuidv4().slice(0, 8)}.jpg`;
moveFile(path.resolve("public", "imagenes", nombreFoto), async (err) => {
if (err) {
res.status(500).send(err);
return;
}
try {
const data = await postParticipantesQuery({
email,
nombre,
password,
anos_experiencia,
especialidad,
foto: nombreFoto,
estado: false,
});
console.log(data);
res.status(200).send("exito");
} catch (error) {
res.status(500).send(error);
}
});
} catch (error) {
console.log(error);
res.status(500).send(error);
}
}
```
### 4. Implementar seguridad y restricción de recursos o contenido con JWT (2 Puntos)
Implemento creación de token en el login de usuario tal y como muestro a continuación:
```js
export default async function loginUser(req, res) {
try {
const { email, password } = req.body;
if (!email || !password) {
res.status(400).send("Faltan datos");
return;
}
const participantes = await getAllDataParticipantesQuery();
const participante = participantes.find(
(participante) => participante.email === email
);
if (!participante) {
res.status(400).send("El email no existe");
return;
}
if (participante.password !== password) {
res.status(400).send("La contraseña es incorrecta");
return;
}
jwt.sign(participante, secretKey, { expiresIn: 2 * 60 }, (err, token) => {
if (err) {
res.status(500).send("Ha ocurrido un error");
return;
}
res.status(200).json({ token });
});
} catch (error) {
console.log(error);
res.status(500).send(error);
}
}
```
Implemento verificación de token al editar o eliminar data del participante. En este caso muestro la verificación de token al eliminar a participante:
```js
export default async function deleteParticipante(req, res) {
try {
const token = req.headers.authorization.split(" ")[1];
jwt.verify(token, process.env.SECRET_KEY_JWT, async (err, decoded) => {
if (err) {
if (err.message === "jwt expired") {
res.status(401).send("El token ha expirado");
return;
}
res.status(401).send("Token inválido");
return;
}
const email = decoded.email;
const data = await deleteParticipanteQuery(email);
console.log(!data);
if (!data) {
res.status(404).send("Participante no encontrado");
return;
}
console.log(path.resolve("public", "imagenes", data.foto));
fs.unlink(path.resolve("public", "imagenes", data.foto), (err) => {
if (err) {
console.log(err);
res.status(500).send("Error al borrar la imagen");
return;
}
res.status(200).send("Participante eliminado");
});
});
} catch (error) {
console.log(error.message);
res.status(500).send(error);
}
}
```
## Extra
### 1. Ruta para resetear la data en la base de datos y borrar las imagenes
He creado la siguiente ruta **reset** siguiente:
```js
router.get("/reset", resetData);
```
La cual utiliza el siguiente middleware:
```js
export default async function resetData(req, res) {
try {
const data = await resetDataQuery();
if (data === "exito") {
const files = await fs.readdir(path.resolve("public", "imagenes"));
for (const file of files) {
await fs.unlink(path.resolve("public", "imagenes", file));
}
res.status(200).send("Data Reseteada con exito !");
}
} catch (error) {
res.status(500).send(error);
}
}
```
La que a su vez utiliza la siguiente función **resetDataQuery**:
```js
export default async function resetDataQuery() {
try {
const query = "DELETE FROM prueba_skate_park_skaters;";
await pool.query(query);
return "exito";
} catch (error) {
throw error;
}
}
```
### 2. Script para resetear la data cada 30 minutos
```js
setInterval(async () => {
try {
const data = await resetDataQuery();
if (data === "exito") {
const files = await fs.readdir(path.resolve("public", "imagenes"));
for (const file of files) {
await fs.unlink(path.resolve("public", "imagenes", file));
}
console.log("Data Reseteada con exito !");
}
} catch (error) {
console.log("Error al resetear la data", error.message);
}
}, 1800000);
```