-
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
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.
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.
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
-
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.
-
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
-
-
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.
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.
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í.
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 |
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 |
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 |
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.
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.
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.
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
.
MONGO_URI_TEST = 'Your MongoDB testing DB'
MONGO_URI = 'Your MongoDB deploy DB'
DB_TEST = true/false
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");
}
};
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.
-
Documentacion de API con Swagger
-
Testing con Jest y Supertest