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
- Solución Completa de la Prueba - Skate Park
- Tabla de Contenido
- Librerías utilizadas
- Deploy
- Requisitos
- Diagrama de Flujo
- Soluciones
- Extra
Librerias Utilizadas |
---|
bcrypt |
bootstrap-icons |
express |
express-fileupload |
express-handlebars |
jsonwebtoken |
pg |
uuid |
El proyecto es 100% funcional y esta operativo en la web. Lo he desplegado en Render en el siguiente link
Para manipular la creación, edición, lectura y eliminación de participantes he creado la siguiente API REST:
router.get("/participantes", getParticipantes);
router.post("/participante", postParticipante);
router.delete("/participante", deleteParticipante);
router.put("/participante", putEditParticipante);
router.patch("/participante", patchEditParticipante);
He servido contenido dinámico con express-handlebars por ejemplo la página de Admin para la cual he utilizado el siguiente código:
Implemento la librería express-fileupload al cargar un archivo en la creación de un participante:
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);
}
}
Implemento creación de token en el login de usuario tal y como muestro a continuación:
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:
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);
}
}
He creado la siguiente ruta reset siguiente:
router.get("/reset", resetData);
La cual utiliza el siguiente middleware:
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:
export default async function resetDataQuery() {
try {
const query = "DELETE FROM prueba_skate_park_skaters;";
await pool.query(query);
return "exito";
} catch (error) {
throw error;
}
}
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);