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
19 changes: 19 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"env": {
"commonjs": true,
"es6": true,
"node": true
},
"extends": [
"airbnb-base"
],
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"parserOptions": {
"ecmaVersion": 2018
},
"rules": {
}
}
70 changes: 50 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,50 @@
# Unit 7 Problem Set #4
## Full Stack To-Do w/ Authentication

## Project Overview
The purpose of this Problem Set is to merge your work from Problem Set 7.1 and Problem Set 7.3. You will rebuild your MVC To-Do list from 7.1 using the RESTful API that you created in 7.3. Instead of sending rendered templates in response to an incoming request, you will make AJAX requests to your backend, handle JSON responses, and render to-do's to the DOM.

### Project Requirements
* Your To-Do list app must handle multiple users.
* Users must register with a username and a password.
* Users should be able to login with a username and password.
* Each user has their own To-Do List, to which they can create, update, view, and delete To-Do's.
* User must be able to logout and be redirected back to the login screen.

## Submission Directions
1. Fork, clone, and create your project in this repo.
2. Update this README so that instead of project directions, it houses the documentation for your project. It can be very simple. [Here's an example](https://github.com/emtes/assignment-todo-list) from Enmanuel!
3. Deploy to Heroku and be sure to include the project URL in your README.

### Due Date
Tuesday, April 7 at 9AM

```sql
CREATE TABLE tasks(
id SERIAL PRIMARY KEY,
user_id int REFERENCES users(id),
task_name text ,
task_description text ,
due_date date ,
is_complete boolean DEFAULT false NOT NULL
) ;
```


```sql
CREATE TABLE users(
id SERIAL PRIMARY KEY,
email text ,
password text
);
```

```
SELECT users.username , tasks.task_name, tasks.task_description , tasks.due_date , tasks.is_complete
FROM users JOIN tasks
ON users.id = tasks.user_id
;
```


**Documentation still needed**
Feel free to use the SQL statements to create quick tables

C: Create tasks (taskName, task description,due date, is complete)

R: Display tasks (all inputted tasks)

U: Update tasks (Change task, extended description, due date or mark complete)
Note: You can only mark complete once

D: Delete tasks (Delete task for list of tasks)




Getting Started
This application is deployed [here](https://quiet-chamber-63376.herokuapp.com/login)

If you want to run it locally, clone this repository and run npm install. Then, run npm start or node app.js.


58 changes: 58 additions & 0 deletions app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
const express = require('express');

const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser');

const userController = require('./controllers/user');
const taskController = require('./controllers/to-do');

const app = express();

const port = process.env.PORT || 8080;

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());


app.use(express.static('public'));

app.get('/', (req, res) => {
res.redirect('/login');
});

app.get('/register', userController.register);

app.post('/register', userController.authorize);

app.get('/login', userController.login);

app.get('/logout', userController.logout);

app.post('/login', userController.verifyLogin);

app.use(userController.authenticate);

// sends a static html file with "tasks value from database"
app.get('/showTask', userController.showTasks);

app.get('/tasks', taskController.getAll);

app.post('/tasks', taskController.createTask);

app.get('/tasks/:id', taskController.getTaskById);

// update tasks
app.post('/tasks/:id', taskController.updateTask);

app.delete('/tasks/:id', taskController.deleteTask);

// Marks a compelete only once
app.put('/markComplete/:id', taskController.markComplete);

// sends a static html file with table vaules
app.get('/addTask', userController.showTaskForm);



app.listen(port, () => console.log(`Server listening on port ${port}...`));
78 changes: 78 additions & 0 deletions controllers/to-do.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
const Task = require('../models/To-do');


const getAll = (req, res) => {
Task.getAll()
.then((data) => res.status(200).json(data.rows))
.catch((err) => {
console.log(err);
res.status(500).send('500 Internal Server Error');
});
};

const getTaskById = (req, res) => {
const { id } = req.params;
Task.getTask(id)
.then((data) => res.status(200).json(data.rows))
.catch((err) => {
console.log(err);
res.status(500).send('500 Internal Server Error');
});
};

const createTask = (req, res) => {
const {
userId, taskName, taskDescription, dueDate, isComplete,
} = req.body;
Task.createTask(userId, taskName, taskDescription, dueDate, isComplete)
.then(() => Task.getTask(userId))
.then((data) => res.status(201).json(data.rows[0]))
.catch((err) => {
console.log(err);
res.status(500).send('500 Internal Server Error');
});
};

const updateTask = (req, res) => {
const { id } = req.params;
const {
taskName, taskDescription, dueDate, isComplete,
} = req.body;
Task.updateTask(id, taskName, taskDescription, dueDate, isComplete)
// .then(() => res.status(200).send('200 Successfully Updated Task'))
.then(() => res.redirect('/showTask'))
.catch((err) => {
console.log(err);
res.status(500).send('500 Internal Server Error');
});
};

const deleteTask = (req, res) => {
const { id } = req.params;
Task.deleteTask(id)
.then(() => res.status(200).send('200 Successfully Deleted Task'))
.catch((err) => {
console.log(err);
res.status(500).send('500 Internal Server Error');
});
};

const markComplete = (req, res) => {
const { id, isComplete } = req.params;
Task.markComplete(id, isComplete)
.then(() => Task.getTask(id))
.then(() => res.status(200).send('200 Successfully Completed Task'))
.catch((err) => {
console.log(err);
res.status(500).send('500 Internal Server Error');
});
};

module.exports = {
getAll,
getTaskById,
createTask,
updateTask,
deleteTask,
markComplete,
};
124 changes: 124 additions & 0 deletions controllers/user.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const path = require('path');
const User = require('../models/User');

const authorize = async (req, res) => {
const { email, password, username } = req.body;
try {
const encrypted = await User.encrypt(password);
await User.add(email, encrypted, username);
res.redirect('/');
} catch (err) {
console.log(err);
res.send(err);
}
};

// This function is responsible for decoding the cookie and authenticating the user
const authenticate = async (req, res, next) => {
if (!req.cookies.userToken) {
res.status(401).send('Unauthorized Users');
return res.redirect('/login');
}

const payload = jwt.verify(req.cookies.userToken, 'secret');
console.log('Payload:', payload);
const { email, password } = payload;
try {
const user = await User.getByEmail(email);
if (!user) {
return res.status(401).send('Unauthorized User');
}

if (password === user.password) {
return next();
}
return res.status(401).send('Unauthorized User');
} catch (err) {
console.log(err);
return res.send(err);
}
};

// This function validate login and set the cookie for a user
const verifyLogin = async (req, res, next) => {
const { email, password } = req.body;

try {
const user = await User.getByEmail(email);
if (!user) {
return res.status(401).send('Unauthorized user');
}

const validPassword = await bcrypt.compare(password, user.password);
if (validPassword) {
return jwt.sign({
email, password: user.password, userId: user.id, iat: Math.floor(Date.now() / 1000) + (60 * 60),
}, 'secret', (err, encryptedPayload) => {
if (err) {
res.status(403).send('Error');
}
res.cookie('userToken', encryptedPayload, { httpOnly: true });
res.status(200);
res.redirect('/showTask');
});
}
return res.status(403).send('Wrong Password');
} catch (err) {
console.log(err);
return res.status(500).send('Internal Server Error');
}
};

// This function is responsible for decoding the cookie and returning user tasks
const getUserTasks = (req, res) => {
const payload = jwt.verify(req.cookies.userToken, 'secret');
console.log('payload at line 80:', payload);
const { userId } = payload;

User.getUserTasks(userId)
.then((data) => res.status(200).json(data))
.catch((err) => {
console.log(err);
res.status(500).send('500 Internal Server Error');
});
};

const showTaskForm = (req, res) => {
res.sendFile(path.join(__dirname, '../views', 'addTask.html'));
};

const showTasks = (req, res) => {
res.sendFile(path.join(__dirname, '../views', 'tasks.html'));
};

const register = (req, res) => {
res.sendFile(path.join(__dirname, '../views', 'register.html'));
};

const login = (req, res) => {
res.sendFile(path.join(__dirname, '../views', 'index.html'));
};

const updateTaskForm = (req, res) => {
res.sendFile(path.join(__dirname, '../views', 'updateTask.html'));
};

const logout = (req,res) =>{
res.clearCookie('userToken');
res.redirect('/');
};

module.exports = {
authorize,
authenticate,
getUserTasks,
login,
register,
verifyLogin,
showTaskForm,
showTasks,
updateTaskForm,
logout,
};
13 changes: 13 additions & 0 deletions db.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const { Pool } = require('pg');

const pool = new Pool({
// user: 'ec2-user',
// host: '/var/run/postgresql',
// database: 'user_tasklist',
// port: 5432,
connectionString: process.env.DATABASE_URL,
});

module.exports = {
query: (text, params) => pool.query(text, params),
};
Loading