Skip to content

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

Notifications You must be signed in to change notification settings

waldohidalgo/skate_park_app

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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

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

Requisitos

Requisitos 1 y 2 Requisitos 3 y 4 Requisito 5

Diagrama de Flujo

1.Página Home

Página Home

2.Página Registro

Página Registro

2.1.Registro de Participante Exitoso

Registro de Participante Exitoso

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

Verificación de Registro exitoso de Participante Exitoso Front

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

Verificación de Registro exitoso de Participante Exitoso Back

3.Página LogIn de Participante

Página LogIn de Participante

3.1.LogIn exitoso de Participante

LogIn exitoso de Participante

3.2.Página Cuenta de Usuario

Página Cuenta de Usuario

3.3.Token Expirado después de 2 minutos

Token Expirado después de 2 minutos

3.4.Alerta Edición exitosa

Alerta Edición exitosa

3.5.Verificación edición exitosa front end

Verificación edición exitosa front end

3.6.Verificación edición exitosa back end

Verificación edición exitosa back end

4.Página Admin

Página Admin

4.1.Alerta Cambio de estado exitoso de participante por Admin

Alerta Cambio de estado exitoso de participante por Admin

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

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

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

5.Alerta de Eliminación de Participante exitosa

Alerta de Eliminación de Participante exitosa

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

Verificación de Eliminación de participante Front End

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

Verificación de Eliminación de participante Back End

6.Alerta Archivo Muy Pesado

Alerta Archivo Muy Pesado

7.Alerta Tipo de Archivo No Permitido

Alerta Tipo de Archivo No Permitido

8.Alerta password y password repetida no coinciden

Alerta password y password repetida no coinciden

9.Página 404

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:

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:

{{> header}}
<main class="py-3 main_page_admin">
  <h2>Administración</h2>
  <hr class="w-50" />
  <div class="table-responsive">
    <table class="table w-50 m-auto table-dark">
      <thead>
        <tr>
          <th scope="col">#</th>
          <th scope="col">Foto</th>
          <th scope="col">Nombre</th>
          <th scope="col">Años de experiencia</th>
          <th scope="col">Especialidad</th>
          <th scope="col">Estado</th>
        </tr>
      </thead>
      <tbody>
        {{#if ( arrayVacio participantes) }}
        <tr>
          <td colspan="6">No hay participantes</td>
        </tr>
        {{else}}
        {{#each participantes}}
        <tr>
          <th scope="row">{{addOne @index}}</th>
          <td>
            <div class="participante_foto">
              <img
                src="/public/imagenes/{{ this.foto }}"
                alt="Imagen de {{ this.nombre }}"
              />
            </div>
          </td>
          <td>{{ this.nombre }}</td>
          <td>{{ this.anos_experiencia }}</td>
          <td>{{ this.especialidad }}</td>
          <td>
            {{#if this.estado }}
            <input
              data-email="{{ this.email }}"
              class="aprobado_checkbox"
              type="checkbox"
              checked
            />
            {{else}}
            <input
              data-email="{{ this.email }}"
              class="aprobado_checkbox"
              type="checkbox"
            />
            {{/if}}
          </td>
        </tr>
        {{/each}}
        {{/if}}
      </tbody>
    </table>
  </div>
</main>
{{> footer}}

<script type="module" src="/public/js/pages/admin.js"></script>

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:

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:

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);
  }
}

Extra

1. Ruta para resetear la data en la base de datos y borrar las imagenes

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;
  }
}

2. Script para resetear la data cada 30 minutos

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);

About

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

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published