Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .env.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
PORT=3000

#DATABASE
MONGO_USER=
MONGO_PASSWORD=
MONGO_CNN=

#JWT
JWT_SECRET=x11E6xalIvOxEVq2Nf5Bc4m
5 changes: 3 additions & 2 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import express from 'express';
import dotenv from 'dotenv';
import dbConnect from './src/config/db.config.js';
import taskSeed from './src/seed/task.seed.js';
import usersSeed from './src/seed/user.seed.js';

dotenv.config();

Expand All @@ -20,9 +21,9 @@ app.use('/api/v1/users', userRouter);
app.use('/api/v1/auth', authRouter);


//Seed
//Seeds
app.use('/api/v1/seed', taskSeed);

app.use('/api/v1/seed', usersSeed)



Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,7 @@
"start": "node app",
"dev": "nodemon --exec \"cls && node app\""
},
"type": "module"
"type": "module",
"author": "Juan Sebastián Astudillo Ordoñez"
}

69 changes: 69 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# To-Do API

## Description
This is a to-do task API, in its first version. The application includes authentication, route protection with JWT, user creation, and relationships between tasks and users.

## Project Status
In development

## Features
- User authentication.
- Route protection using JWT.
- Task creation and management.
- Relationship between tasks and users.
- **Future Features:**
- Viewing tasks related to the user.
- Role validation.
- Email confirmation.
- Execute seeds in a single endpoint.

## Technologies Used
- Node.js
- Express
- MongoDB

## Installation
1. Clone the repository.
2. Install dependencies:
```bash
yarn install
```
3. Create a cluster in MongoDB Atlas.
4. Rename `.env.template` to `.env` and fill in the environment variables.
5. Run the application in development:
```bash
yarn dev
```

6. Run the user and task seeds:
```bash
http://localhost:3000/api/v1/seed/users
http://localhost:3000/api/v1/seed/tasks
```

## Usage
### Endpoints

| Method | Endpoint | Description |
|--------|-----------------------------------------------------|-------------------------------------------------------|
| POST | `http://localhost:3000/api/v1/users/register` | Register a user (requires email and password in the body) |
| GET | `http://localhost:3000/api/v1/users/verify/:token` | Verify a user with their token |
| POST | `http://localhost:3000/api/v1/auth/login` | Log in, returns a JWT |
| POST | `http://localhost:3000/api/v1/tasks` | Create a task, requires a JWT in the header |
| GET | `http://localhost:3000/api/v1/tasks/:term` | Search for a specific task by title or status |
| PUT | `http://localhost:3000/api/v1/tasks/:id` | Update a task by its ID, requires JWT |
| GET | `http://localhost:3000/api/v1/tasks?limit={number}&offset={number}` | Get all tasks with pagination |
| DELETE | `http://localhost:3000/api/v1/tasks/:id` | Delete a task, requires JWT |

## License
This project is licensed under the MIT License.

## Credits
- Juan Sebastián Astudillo Ordoñez

## Contact
You can contact me at [sebastian.dev0708@gmail.com](mailto:sebastian.dev0708@gmail.com).

## Changelog
### v1.0.0
- First version released.
8 changes: 6 additions & 2 deletions src/controllers/task.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,13 @@ export const createTask = async (req = request, res = response) => {
try {

const task = await Task.create({
//Aqui establezco el id del usuario autenticado
author: req.user._id,
status: status ? status.toUpperCase() : undefined,
...data
});


return res.status(201).json({ task });

} catch (error) {
Expand Down Expand Up @@ -45,7 +48,7 @@ export const findTaskByTerm = async (req = request, res = response) => {
});
}

if (task.length === 0)
if (!task || task.length === 0)
return res.status(404).json({
error: `Task with title, id, or status '${term}' not found`
});
Expand Down Expand Up @@ -89,13 +92,14 @@ export const updateTask = async (req = request, res = response) => {
}
}

//TODO: Añadir Ordenamiento

export const findAllPaginated = async (req = request, res = response) => {

const { limit = 5, offset = 0 } = req.query;

try {
const results = await Task.find()
.populate('author', 'username _id')
.skip(offset)
.limit(+limit)

Expand Down
6 changes: 5 additions & 1 deletion src/helpers/generate-jwt.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import jwt from 'jsonwebtoken';

export const generateJwt = async (uid) => {
try {
return await jwt.sign({ uid }, process.env.JWT_SECRET);
return await jwt.sign(
{ uid },
process.env.JWT_SECRET,
{ expiresIn: '4h' }
);
} catch (error) {
console.error(error)
}
Expand Down
45 changes: 45 additions & 0 deletions src/middlewares/validate-jwt.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import jwt from 'jsonwebtoken';
import User from '../models/user.model.js';

//middleware para validar JWT
export const validateJWT = async (req, res, next) => {

const token = req.header('to-do-token');

if (!token) {
return res.status(401).json({
error: 'No token provided'
});
}

try {

//Extraer el payload del token
const { uid } = jwt.verify(token, process.env.JWT_SECRET);

//leer el usuario correspondiente al uid
const user = await User.findById(uid);

//Verificar si el usuario existe
if(!user || !user.status){
return res.status(404).json({
error:`User with id ${uid} doesn't exists`
});
}

// ???
req.user = user;

next();



} catch (error) {
console.error(error);
res.status(500).json({
error: 'Something went broke.'
})

}

}
6 changes: 5 additions & 1 deletion src/models/task.model.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Schema, model } from 'mongoose';
import mongoose, { Schema, model } from 'mongoose';

const TaskSchema = Schema({
title: {
Expand All @@ -18,6 +18,10 @@ const TaskSchema = Schema({
type: String,
enum: ['PENDING', 'IN PROGRESS', 'COMPLETED'],
default: 'PENDING'
},
author: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Users'
}
},
{ timestamps: true }
Expand Down
7 changes: 6 additions & 1 deletion src/routes/task.routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ import { createTask, deleteTask, findAllPaginated, findTaskByTerm, updateTask }
import { checkValidationResult } from "../middlewares/check-validation-result.js";
import { check } from "express-validator";
import { isValidEnum } from "../helpers/check-enum.js";
import { validateJWT } from "../middlewares/validate-jwt.js";
const taskRouter = Router();

//TODO: Validar roles [ADMIN, USER]

taskRouter.post('/', [
validateJWT,
check('title', 'Title must be more than 5 characters.')
.isLength({ min: 5, max: 50 }),

Expand All @@ -27,6 +30,7 @@ taskRouter.post('/', [


taskRouter.put('/:id', [
validateJWT,
check('id', 'Invalid Mongo ID').isMongoId(),
check('title', 'Title must be more than 5 characters.')
.optional()
Expand All @@ -49,7 +53,8 @@ taskRouter.put('/:id', [


taskRouter.delete('/:id', [
check('id','Invalid Mongo Id').isMongoId(),
validateJWT,
check('id', 'Invalid Mongo Id').isMongoId(),
checkValidationResult
], deleteTask);

Expand Down
19 changes: 14 additions & 5 deletions src/routes/user.routes.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
import { check } from "express-validator";
import { confirmAccount, createAccount, desactiveUser } from "../controllers/user.controller.js";
import { Router } from "express";

const userRouter = Router();

//TODO:AÑADIR VALIDACIONES
userRouter.post('/register', createAccount);

//TODO:AÑADIR VALIDACIONES
userRouter.post('/register', [
check('username', 'username must be more than 5 characters')
.isString({ min: 5 }),
check('email', 'invalid email format').isEmail(),
check('password', 'password is required').isString({ min: 6 })

], createAccount);


userRouter.get('/verify/:token', confirmAccount);

//TODO:AÑADIR VALIDACIONES
userRouter.delete('/:id', desactiveUser);

userRouter.delete('/:id', [
check('id', 'Invalid mongoId.').isMongoId()
], desactiveUser);

export default userRouter;
77 changes: 77 additions & 0 deletions src/seed/task.seed.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Router } from "express";
import Task from "../models/task.model.js";
import User from '../models/user.model.js'

const taskSeed = Router();

Expand Down Expand Up @@ -99,13 +100,89 @@ const tasks = [
description: "Conocer a otros desarrolladores y aprender",
status: "PENDING",
deadLine: new Date("2024-10-30")
},
{
title: "Estudiar un nuevo framework",
description: "Aprender sobre un nuevo framework de desarrollo web",
status: "PENDING",
deadLine: new Date("2024-11-15")
},
{
title: "Crear un portafolio en línea",
description: "Diseñar y desarrollar un portafolio para mostrar mis proyectos",
status: "PENDING",
deadLine: new Date("2024-11-30")
},
{
title: "Leer un libro de programación",
description: "Terminar de leer 'Clean Code' de Robert C. Martin",
status: "PENDING",
deadLine: new Date("2024-12-15")
},
{
title: "Participar en un hackathon",
description: "Unirse a un hackathon local y colaborar con otros",
status: "PENDING",
deadLine: new Date("2024-11-20")
},
{
title: "Actualizar el CV",
description: "Revisar y actualizar el currículum vitae",
status: "PENDING",
deadLine: new Date("2024-12-01")
},
{
title: "Aprender sobre bases de datos NoSQL",
description: "Explorar MongoDB y otras bases de datos NoSQL",
status: "PENDING",
deadLine: new Date("2024-12-05")
},
{
title: "Tomar un curso de JavaScript avanzado",
description: "Inscribirse en un curso en línea para mejorar mis habilidades",
status: "PENDING",
deadLine: new Date("2024-12-10")
},
{
title: "Conectar con un mentor",
description: "Buscar y establecer contacto con un mentor en la industria",
status: "PENDING",
deadLine: new Date("2024-11-25")
},
{
title: "Implementar un proyecto personal",
description: "Desarrollar un proyecto personal para aplicar lo aprendido",
status: "PENDING",
deadLine: new Date("2024-12-20")
},
{
title: "Revisar y practicar algoritmos",
description: "Dedicar tiempo a resolver problemas de algoritmos y estructuras de datos",
status: "PENDING",
deadLine: new Date("2024-12-30")
}
];

taskSeed.get('/tasks', async (req, res) => {

try {

const users = await User.find();

//Extraer los ids para asignarselos a cada task
const ids = users.reduce((acc, user) => {
acc.push(user._id);
return acc;
}, []);

//Asignar un id aleatorio a cada usuario
//Simulando que estos crearon dicha tarea
tasks.forEach(task => {
const randomIndex = Math.floor(Math.random() * ids.length);
task.author = ids[randomIndex];
});


await Task.deleteMany();
await Task.insertMany(tasks);

Expand Down
Loading