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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
node_modules
.vscode/*
.vscode/*
config/.env
58 changes: 34 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,44 +1,54 @@
# Introduction

A Simple ToDo App is built using the MVC Architecture, we have also implemented "authorization" so folx can sign up, customize & personalize the app

---
![todos-app](https://github.com/user-attachments/assets/8d6ac30c-e0dc-472d-b4dd-b1d56726dfb6)

> Be sure to add that lovely star 😀 and fork it for your own copy

---

# Objectives
# Introduction

- It's a beginner level app created to understand how MVC concept and logins are added
This is forked from a Simple ToDo App built using the MVC Architecture, implementing "authorization" so folx can sign up, customize & personalize the app

---

# Who is this for?
# Features

- It's for beginners & intermediates with little more experience, to help understand the various aspects of building a node app with some complex features
- User sessions: sign up, log in, personalized greeting and unique todo list
- Todos: adding, editing, categorizing, sorting, and deleting functions
- Special "De-stress" function when there are too many things to do...
- Packages have been updated and deprecated code adjusted from the original fork

---

# Packages/Dependencies used

bcrypt, connect-mongo, dotenv, ejs, express, express-flash, express-session, mongodb, mongoose, morgan, nodemon, passport, passport-local, validator
# Packages/Dependencies used

- bcrypt - hashing/salting/encrypting in order to not have plain text passwords stored in the database
- connect-mongo - helps with session storage in the database
- dotenv - to use environment variable files
- ejs - templating for rendering dynamic data to html
- express - node framework for easier setup
- express-flash - flash messages without request redirection (error messaging for forms)
- express-session - using cookies along with the db to keep track of logged in users
- mongodb - to connect to mongo database
- mongoose - easily set up schemas for data being sent and stored in mongodb
- morgan - logging activity in the console
- nodemon - auto restart server after changes
- passport - strategies for authentication
- passport-local - allow user to make an account with sign in data instead of a different strategy
- validator - checks validity of strings to make sure user will enter the required data

---

# Install all the dependencies or node packages used for development via Terminal
# Local setup

Install all the dependencies or node packages used for development via Terminal
`npm install`

`npm install`
Start server with nodemon
`npm start`

---

# Things to add

- Create a `.env` file and add the following as `key: value`
- PORT: 2121 (can be any port example: 3000)
- DB_STRING: `your database URI`
---

Have fun testing and improving it! 😎


- Create a `.env` file and add the following as `key: value`
- PORT: 2121 (can be any port example: 3000)
- DB_STRING: `your database URI`
***
2 changes: 0 additions & 2 deletions config/.env

This file was deleted.

4 changes: 1 addition & 3 deletions config/database.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ const connectDB = async () => {
try {
const conn = await mongoose.connect(process.env.DB_STRING, {
useNewUrlParser: true,
useUnifiedTopology: true,
useFindAndModify: false,
useCreateIndex: true
useUnifiedTopology: true
})

console.log(`MongoDB Connected: ${conn.connection.host}`)
Expand Down
34 changes: 23 additions & 11 deletions config/passport.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,43 @@ const mongoose = require('mongoose')
const User = require('../models/User')

module.exports = function (passport) {
passport.use(new LocalStrategy({ usernameField: 'email' }, (email, password, done) => {
User.findOne({ email: email.toLowerCase() }, (err, user) => {
if (err) { return done(err) }
passport.use(new LocalStrategy({ usernameField: 'email' }, async (email, password, done) => {
try{
const user = await User.findOne({ email: email.toLowerCase() })
if (!user) {
return done(null, false, { msg: `Email ${email} not found.` })
}
if (!user.password) {
return done(null, false, { msg: 'Your account was registered using a sign-in provider. To enable password login, sign in using a provider, and then set a password under your user profile.' })
}
user.comparePassword(password, (err, isMatch) => {
if (err) { return done(err) }
if (isMatch) {
return done(null, user)
}
return done(null, false, { msg: 'Invalid email or password.' })
const isMatch = await new Promise((resolve, reject)=> {
user.comparePassword(password, (err, isMatch) => {
if (err) reject(err)
else resolve(isMatch)
})
})

if (isMatch) {
return done(null, user)
}
return done(null, false, { msg: 'Invalid email or password.' })

} catch (err){
return done(err)
}
}))


passport.serializeUser((user, done) => {
done(null, user.id)
})

passport.deserializeUser((id, done) => {
User.findById(id, (err, user) => done(err, user))
passport.deserializeUser(async (id, done) => {
try{
const user = await User.findById(id)
done(null, user)
} catch (err) {
done(err)
}
})
}
10 changes: 10 additions & 0 deletions constants/labels.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module.exports = {
todoLabels :{
"🧹" : "Housework",
"🏢" : "Occupation",
"📖" : "Education",
"🚗" : "Errand",
"🛒" : "Shopping",
"🌼" : "Self Care"
}
}
27 changes: 14 additions & 13 deletions controllers/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ const User = require('../models/User')
})
}

exports.postSignup = (req, res, next) => {
exports.postSignup = async (req, res, next) => {
const validationErrors = []
if (!validator.isEmail(req.body.email)) validationErrors.push({ msg: 'Please enter a valid email address.' })
if (!validator.isLength(req.body.password, { min: 8 })) validationErrors.push({ msg: 'Password must be at least 8 characters long' })
Expand All @@ -74,23 +74,24 @@ const User = require('../models/User')
password: req.body.password
})

User.findOne({$or: [
try{
const existingUser = await User.findOne({$or: [
{email: req.body.email},
{userName: req.body.userName}
]}, (err, existingUser) => {
if (err) { return next(err) }
]})
if (existingUser) {
req.flash('errors', { msg: 'Account with that email address or username already exists.' })
return res.redirect('../signup')
}
user.save((err) => {
if (err) { return next(err) }
await user.save()
await new Promise((resolve, reject) =>
req.logIn(user, (err) => {
if (err) {
return next(err)
}
res.redirect('/todos')
if (err) reject(err)
else resolve()
})
})
})
}
)
res.redirect('/todos')
} catch (err){
return next(err)
}
}
46 changes: 43 additions & 3 deletions controllers/todos.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,55 @@
const Todo = require('../models/Todo')
const { todoLabels } = require('../constants/labels')

module.exports = {
getTodos: async (req,res)=>{
console.log(req.user)
try{
const todoItems = await Todo.find({userId:req.user.id})
const itemsLeft = await Todo.countDocuments({userId:req.user.id,completed: false})
res.render('todos.ejs', {todos: todoItems, left: itemsLeft, user: req.user})
let todoItems


if (req.query.filter){
const filter = req.query.filter
todoItems = await Todo.find({userId:req.user.id, label:filter})
} else{
todoItems = await Todo.find({userId:req.user.id})
}

res.render('todos.ejs', {
todos: todoItems,
left: itemsLeft,
user: req.user,
labels: todoLabels,
filter: req.query.filter
})
}catch(err){
console.log(err)
}
},
createTodo: async (req, res)=>{
try{
await Todo.create({todo: req.body.todoItem, completed: false, userId: req.user.id})
await Todo.create({label: req.body.labelEmojiSelect, todo: req.body.todoItem, completed: false, userId: req.user.id})
console.log('Todo has been added!')
res.redirect('/todos')
}catch(err){
console.log(err)
}
},
editTodo: async (req, res)=>{
try{
await Todo.findOneAndUpdate({_id:req.body.todoId},{label:req.body.labelEmojiSelect, todo: req.body.todoItem})
console.log('Todo has been edited!')
if (req.body.labelEmojiSelect == req.body.currentFilter){
res.redirect(`/todos?filter=${req.body.currentFilter}`)
}else {
res.redirect('/todos')
}

}catch(err){
console.log(err)
}
},
markComplete: async (req, res)=>{
try{
await Todo.findOneAndUpdate({_id:req.body.todoIdFromJSFile},{
Expand Down Expand Up @@ -51,5 +81,15 @@ module.exports = {
}catch(err){
console.log(err)
}
},
clearTodos: async(req,res)=>{
console.log("Destressing... clear all todos")
try{
await Todo.deleteMany({userId: req.user.id})
console.log('All todos deleted')
res.json('All Clear! Doesn\'t that feel better?')
}catch(err){
console.log(err)
}
}
}
4 changes: 4 additions & 0 deletions models/Todo.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
const mongoose = require('mongoose')

const TodoSchema = new mongoose.Schema({
label: {
type: String,
required: true,
},
todo: {
type: String,
required: true,
Expand Down
Loading