CANTASTIK

  • Sobre el proyecto

    • Tecnologías utilizadas

    • Instalación y despliegue

    • Origen

    • Objetivos

    • Concepto e inspiración

    • Organizacion

  • Documentacion de API

  • Retos presentados

    • Borrar usuario y todo su rastro

    • Validacion de email

    • Middelware de errores

    • Seleccion de DB

    • Problemas varios con email de confirmacion

  • En el tintero

  • Autor

Sobre el proyecto

Tecnologias utilizadas

portada

Este proyecto está basado en un servidor de NodeJs con Express. La base de datos es MongoDB y para trabajar con ella usamos Mongoose. También hemos utilizado otros paquetes de NPM como: bcryptjs, dotenv, jsonwebtoken, multer, nodemailer y validator.

Instalación y despliegue

En el caso de querer usar el proyecto en un servidor local tienes que hacer los siguientes pasos:

  • Introducir en la terminal el comando https://github.com/MrSetOne/SocialNetwork_MongoDB.git para descargarte el repositorio.

  • Dentro de la carpeta del repositorio ejecuta npm i para instalar las dependencias necesarias.

  • En la raiz del proyecto encontraras el archivo ".env-example", este archivo carga las configuraciones de la base de datos, puerto local, contraseña de JsonWebToken y los datos de nodemailer. Haz una copia, llamala ".env" y rellena los campos como se indica en el archivo, ten en cuenta que para usar nodemailer es necesario una cuenta de Outlook

  • Dentro de la carpeta del repositorio ejecuta npm start para ejecutar el servidor en local.

Origen

Es un proyecto planteado como ejercicio del Bootcamp de FullStack en TheBridge, consiste en generar el BackEnd de una red social basado principalmente en NodeJS y MongoDB. Parte de la intencion de este ejercicio es trabajar la buenas practicas de "clean code" y el uso de ramas en GIT

Objetivos

Endpoints

  • Posts

    • Endpoint para crear un post( tiene que estar autenticado)

    • Endpoint para actualizar un post ( tiene que estar autenticado)

    • Endpoint para eliminar un post( tiene que estar autenticado)

    • Endpoint para traer todos los posts junto a los usuarios que hicieron ese post y junto a los comentarios del post

    • Endpoint para buscar post por nombre

    • Endpoint para buscar post por id

    • Implementa validación a la hora de crear un post para que se rellene todos los campos(salvo la imagen, que no sea requerida) y si no se hace que devuelva un mensaje

    • Paginación de 10 en 10

    • Likes:

      • Endpoint para dar un like a un post

      • Endpoint para quitar like a un post

  • Comments

    • Endpoint para crear un comentario en un determinado post
  • Usuarios

    • Endpoint para registrar un usuario utilizando bcrypt

    • Implementa el correo de confirmación para el registro

    • Endpoint para login(utilizando bcrypt +JWT)

    • Validación en el login:

      • Si no has confirmado tu correo no puedes conectarte
    • Endpoint que nos traiga la información del usuario conectado

    • Endpoint para el logout

    • Implementa validación a la hora de crear un usuario para que se rellene todos los campos y si no se hace que devuelva un mensaje

  • Backend disponible en producción (Heroku).

  • Middleware para comprobar la autoría del post a la hora de editar/eliminar el mismo.

Extras

  • Middleware para comprobar la autoría del comentario a la hora de editar/eliminar el mismo.

  • Implementa el middleware multer para poder adjuntar imágenes al crear o actualizar posts.

  • Implementa el middleware multer para poder adjuntar imágenes al crear o actualizar un usuario.

  • Implementación de followers:

    • Que puedas seguir a otros usuarios

    • Que puedas dejar de seguir a otros usuarios

  • El Endpoint que nos trae la información del usuario conectado, además que nos traiga los posts y el número de seguidores que tiene

  • Endpoint que nos trae la información del usuario conectado junto a sus post y número de followers, también que nos muestre el nombre de los followers que siguen al usuario conectado

  • El endpoint que trae todos los posts junto a los usuarios que hicieron ese post y junto a los comentarios del post que también traiga los usuarios que hicieron los comentarios

  • Endpoint para buscar usuario por nombre

  • Endpoint para buscar usuario por id

  • Aplica lo aprendido de testing con Jest y Supertest en alguna parte de tu proyecto, por ejemplo en la parte encargada de los endpoints de usuario

  • Crea una documentación de tu proyecto

  • Comments

    • CRUD comments

    • Likes

      • Dar un like a un comentario

      • Quitar like a un comentario

Concepto e inspiracion

La idea de esta red social "Cantastik" surge de la necesidad de anonimato que requiere el mundo del graffiti, por falta de tiempo no se han podido realizar algunas implementaciones que dejarian ver mejor el enfoque del proyecto, como podria ser la implementacion de crews.

Organizacion

treloo

Para la organizacion del proyecto he usado trello, con la intencion de que no se me pasase nada por alto, tratando de tenerlo lo mas "cuidado" posible.

Documenteacion de API

Por falta de tiempo la documentacion ha sido realizada con Postman, aunque lo correcto hubiese sido hacerla con Swagger, aquí pongo unas tablas resumiendo los endpoints, aunque tambien pueder ver la documentacion completa haciendo click aquí.

Usuarios

Method EndPoint Auth Params Body Usage Ref
POST localhost:YourPort/users/ NA ✔️ Crear nuevo usuario Link
PUT localhost:YourPort/users/login NA ✔️ Hacer LogIn Link
PUT localhost:YourPort/users/logout User Hacer LogOut Link
PUT localhost:YourPort/users/modify User ✔️ Modificar usuario Link
DELETE localhost:YourPort/users/id/:_id User ✔️ Usuario se borra a si mismo Link
DELETE localhost:YourPort/users/admin/:_id Admin ✔️ Admin borra a usuario Link
GET localhost:YourPort/users/ User Obtener todos los usuarios Link
GET localhost:YourPort/users/admin Admin Obtener toda la informacion de usuarios Link
GET localhost:YourPort/users/session User Obtener informacion de sesión Link
GET localhost:YourPort/users/id/:_id User ✔️ Obtener informacion por id de usuario Link
GET localhost:YourPort/users/user/:username User ✔️ Buscar usuario por nombre Link
PUT localhost:YourPort/users/follow/:_id User ✔️ Dar Follow a usuario Link
PUT localhost:YourPort/users/unfollow/:_id User ✔️ Dar Unfollow a usuario Link
PUT localhost:YourPort/users/admin/:_id Admin ✔️ Hacer a un usuario Admin Link

Posts

Method EndPoint Auth Params Body Usage Ref
POST localhost:YourPort/posts/ User ✔️ Crear un nuevo post Link
PUT localhost:YourPort/posts/id/:_id Author ✔️ ✔️ Modificar un post Link
DELETE localhost:YourPort/posts/admin/id/:_id Admin ✔️ Eliminar un post como admin Link
DELETE localhost:YourPort/posts/id/:_id Author ✔️ Eliminar un post como autor Link
GET localhost:YourPort/posts/?page=:page&limit=:limmit User ✔️ Obtener todos los posts Link
GET localhost:YourPort/posts/title/:title User ✔️ Buscar posts por titulo Link
GET localhost:YourPort/posts/id/:_id User ✔️ Obtener post por id Link
PUT localhost:YourPort/posts/like/id/:_id User ✔️ Dar like a un post Link
PUT localhost:YourPort/posts/unlike/id/:_id User ✔️ Quitar like a un post Link

Comentarios

Method EndPoint Auth Params Body Usage Ref
POST localhost:YourPort/comments/id/:_id User ✔️ ✔️ Crear un comentario Link
PUT localhost:YourPort/comments/id/:_id Author ✔️ ✔️ Modificar un comentario Link
DELETE localhost:YourPort/comments/id/:_id Author ✔️ Autor elimima comentario Link
DELETE localhost:YourPort/comments/admin/id/:_id Admin ✔️ Admin elimina comentario Link
PUT localhost:YourPort/comments/like/id/:_id User ✔️ Añadir like Link
PUT localhost:YourPort/comments/unlike/id/:_id User ✔️ Quitar like Link

Retos presentados

Borrar usuario y todo su rastro

El borrar un usuario es un proceso complejo, ya que un usuario contiene muchas relaciones y algunas de estas relaciones a su vez tienen más relaciones, para soluciona esto he creado un middelware el cual se ejecuta antes de ejecutarse la funcion del controlador.

const deleterUser = async(req, res, next) => {
    try {
        let target = await User.findById(req.params._id);
        if (target.postIds) {
            target.postIds.forEach(async(post) => {
                const targetPost = await Post.findById(post);
                if (targetPost.likes) {
                    targetPost.likes.forEach(async(like) => {
                        await User.findByIdAndUpdate(like, { $pull: { likedPosts: targetPost._id } });
                    })
                }
                if (targetPost.comments) {
                    targetPost.comments.forEach(async(comment) => {
                        const toDelete = await Comment.findById(comment);
                        await User.findByIdAndUpdate(toDelete.author, { $pull: { comments: comment } });
                        await Comment.findByIdAndDelete(comment);
                    })
                }
                await User.findByIdAndUpdate(req.params._id, { $pull: { postIds: post } })
                await Post.findByIdAndDelete(post)
            })
        };
        target = await User.findById(req.params._id);
        if (target.comments) {
            target.comments.forEach(async(comment) => {
                const toDelete = await Comment.findById(comment);
                await Post.findByIdAndUpdate(toDelete.postId, { $pull: { comments: comment } });
                await Comment.findByIdAndDelete(comment);
            })
        };
        target = await User.findById(req.params._id);
        if (target.followers) {
            target.followers.forEach(async(follower) => {
                await User.findByIdAndUpdate(follower, { $pull: { following: target._id } })
            })
        };
        target = await User.findById(req.params._id);
        if (target.following) {
            target.following.forEach(async(follow) => {
                await User.findByIdAndUpdate(follow, { $pull: { followers: target._id } })
            })
        };
        target = await User.findById(req.params._id);
        if (target.likedPosts) {
            target.likedPosts.forEach(async(like) => {
                await Post.findByIdAndUpdate(like, { $pull: { likes: target._id } })
            })
        }
        next()
    } catch (error) {
        res.send({ msg: 'Error en el middelware de borrado', error })
    }
}

Como se puede observar se ejecutan una serie de bucles que recorren las referencia y van eliminando de forma procedimental todos los registros relacionados con ellas.

Este codigo se podria refactorizar, pero por falta de tiempo no ha sido viable hacerlo.

Validacion de email

Para la validacion del email originalmente se propuso usar RegExp, en lugar de eso instalamos la libreria validator, la cual realiza esta verificacion sin necesidad de picar un RegExp con los posibles fallos que esto te pueda acarrear.

Middelware de errores

Para la gestion de errores cree mi propio middelware de errores, basandome en las tipologias que arrojaban estos mismos, descubrí varios patrones que se repetian dependiendo del tipo de error que se arrojase. El codigo resultante fué el siguiente:

const TypeError = (error, req, res, next) => {
    if (error.errors) {
        error.feedback = []
        for (const fail in error.errors) {
            error.feedback.push({ path: error.errors[fail].path, message: error.errors[fail].message });
        };
        console.log(error.feedback);
        return res.status(400).send({
            message: 'Error en la validacion de los campos:',
            feedback: error.feedback
        });
    } else if (error.keyPattern) {
        const failed = Object.keys(error.keyPattern);
        const failedValue = error.keyValue[failed[0]];
        return res.status(400).send({
            message: `El ${failed[0]} ${failedValue} ya está en uso.`,
            failedField: failed[0],
            failedValue
        });

    } else if (error.kind == "ObjectId") {
        return res.send(`El id ${error.value} no existe`)
    } else {
        return res.status(500).send({ message: `Algo ha fallado en el controlador de ${error.origin}`, error });
    }
};

Este middleware te devuelve un error simplificado y mas amigable con el FrontEnd, que al final va a ser el consumidor de nuestra API y base de datos.

Seleccion de DB

El proyecto puede funcionar sobre dos bases de datos, la primera es la de testing y la segunda la del Deploy, esta informacion se encuentra dentro del archivo .env, tambien hemos generado un "falso booleando" (Falso porque shell no acepta este tipo de variable) con el que seleccionamos a que base de datos nos vamos a conectar, esto se realiza a través de el archivo config.js.

.env

MONGO_URI_TEST = 'Your MongoDB testing DB'

MONGO_URI = 'Your MongoDB deploy DB'

DB_TEST = true/false

config.js

const dbConnection = async() => {
    try {
        if (DB_TEST == 'true') {
            await mongoose.connect(MONGO_URI_TEST);
            console.log('Te has conectado a la DB de testing');
        } else {
            await mongoose.connect(MONGO_URI);
            console.log('Te has conectado a la DB del deploy');
        }
        console.log("BBDD conectada con éxito");
    } catch (error) {
        console.error(error);
        throw new Error("Algo ha fallado a la hora de conectar con la BBDD");
    }
};

Problemas varios con email de confirmacion

Para conseguir que funcione Nodemailer tuvimos diversos problemas, en primer lugar optamos por utilizar Gmail como sistema de envio, pero este dominio ha protegido sus cuentas para que no se puedan mandar correos de terceros. Despues optamos por utilizar Outlook, el cual costó bastante de hacerlo funcionar (la configuracion dentro de nodemailer.js es correcta), el problema es que tras algunos correos la cuenta se autoprotege y no te deja seguir utilizandola con Nodemailer, este es el motivo por el cual están comentadas las lineas de la 21 a la 32 del controlador de usuarios, en este caso tocará verificar al usuario a través de la base de datos directamente.

En el tintero

  • Documentacion de API con Swagger

  • Testing con Jest y Supertest

Autor

🍄 Mike L. Sánchez