Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
moses-codes committed Sep 2, 2022
0 parents commit 344c510
Show file tree
Hide file tree
Showing 22 changed files with 3,990 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
.vscode/*
44 changes: 44 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# 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

---

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

# Objectives

- It's a beginner level app created to understand how MVC concept and logins are added

---

# Who is this for?

- It's for beginners & intermediates with little more experience, to help understand the various aspects of building a node app with some complex features

---

# Packages/Dependencies used

bcrypt, connect-mongo, dotenv, ejs, express, express-flash, express-session, mongodb, mongoose, morgan, nodemon, passport, passport-local, validator

---

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

`npm install`

---

# 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! 😎


2 changes: 2 additions & 0 deletions config/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
PORT = 2121
DB_STRING = mongodb+srv:https://demo:[email protected]/todos?retryWrites=true&w=majority
19 changes: 19 additions & 0 deletions config/database.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
const mongoose = require('mongoose')

const connectDB = async () => {
try {
const conn = await mongoose.connect(process.env.DB_STRING, {
useNewUrlParser: true,
useUnifiedTopology: true,
useFindAndModify: false,
useCreateIndex: true
})

console.log(`MongoDB Connected: ${conn.connection.host}`)
} catch (err) {
console.error(err)
process.exit(1)
}
}

module.exports = connectDB
33 changes: 33 additions & 0 deletions config/passport.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const LocalStrategy = require('passport-local').Strategy
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) }
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.' })
})
})
}))


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

passport.deserializeUser((id, done) => {
User.findById(id, (err, user) => done(err, user))
})
}
96 changes: 96 additions & 0 deletions controllers/auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
const passport = require('passport')
const validator = require('validator')
const User = require('../models/User')

exports.getLogin = (req, res) => {
if (req.user) {
return res.redirect('/todos')
}
res.render('login', {
title: 'Login'
})
}

exports.postLogin = (req, res, next) => {
const validationErrors = []
if (!validator.isEmail(req.body.email)) validationErrors.push({ msg: 'Please enter a valid email address.' })
if (validator.isEmpty(req.body.password)) validationErrors.push({ msg: 'Password cannot be blank.' })

if (validationErrors.length) {
req.flash('errors', validationErrors)
return res.redirect('/login')
}
req.body.email = validator.normalizeEmail(req.body.email, { gmail_remove_dots: false })

passport.authenticate('local', (err, user, info) => {
if (err) { return next(err) }
if (!user) {
req.flash('errors', info)
return res.redirect('/login')
}
req.logIn(user, (err) => {
if (err) { return next(err) }
req.flash('success', { msg: 'Success! You are logged in.' })
res.redirect(req.session.returnTo || '/todos')
})
})(req, res, next)
}

exports.logout = (req, res) => {
req.logout(() => {
console.log('User has logged out.')
})
req.session.destroy((err) => {
if (err) console.log('Error : Failed to destroy the session during logout.', err)
req.user = null
res.redirect('/')
})
}

exports.getSignup = (req, res) => {
if (req.user) {
return res.redirect('/todos')
}
res.render('signup', {
title: 'Create Account'
})
}

exports.postSignup = (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' })
if (req.body.password !== req.body.confirmPassword) validationErrors.push({ msg: 'Passwords do not match' })

if (validationErrors.length) {
req.flash('errors', validationErrors)
return res.redirect('../signup')
}
req.body.email = validator.normalizeEmail(req.body.email, { gmail_remove_dots: false })

const user = new User({
userName: req.body.userName,
email: req.body.email,
password: req.body.password
})

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) }
req.logIn(user, (err) => {
if (err) {
return next(err)
}
res.redirect('/todos')
})
})
})
}
5 changes: 5 additions & 0 deletions controllers/home.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
getIndex: (req,res)=>{
res.render('index.ejs')
}
}
55 changes: 55 additions & 0 deletions controllers/todos.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
const Todo = require('../models/Todo')

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})
}catch(err){
console.log(err)
}
},
createTodo: async (req, res)=>{
try{
await Todo.create({todo: req.body.todoItem, completed: false, userId: req.user.id})
console.log('Todo has been added!')
res.redirect('/todos')
}catch(err){
console.log(err)
}
},
markComplete: async (req, res)=>{
try{
await Todo.findOneAndUpdate({_id:req.body.todoIdFromJSFile},{
completed: true
})
console.log('Marked Complete')
res.json('Marked Complete')
}catch(err){
console.log(err)
}
},
markIncomplete: async (req, res)=>{
try{
await Todo.findOneAndUpdate({_id:req.body.todoIdFromJSFile},{
completed: false
})
console.log('Marked Incomplete')
res.json('Marked Incomplete')
}catch(err){
console.log(err)
}
},
deleteTodo: async (req, res)=>{
console.log(req.body.todoIdFromJSFile)
try{
await Todo.findOneAndDelete({_id:req.body.todoIdFromJSFile})
console.log('Deleted Todo')
res.json('Deleted It')
}catch(err){
console.log(err)
}
}
}
10 changes: 10 additions & 0 deletions middleware/auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module.exports = {
ensureAuth: function (req, res, next) {
if (req.isAuthenticated()) {
return next()
} else {
res.redirect('/')
}
}
}

18 changes: 18 additions & 0 deletions models/Todo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const mongoose = require('mongoose')

const TodoSchema = new mongoose.Schema({
todo: {
type: String,
required: true,
},
completed: {
type: Boolean,
required: true,
},
userId: {
type: String,
required: true
}
})

module.exports = mongoose.model('Todo', TodoSchema)
36 changes: 36 additions & 0 deletions models/User.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
const bcrypt = require('bcrypt')
const mongoose = require('mongoose')

const UserSchema = new mongoose.Schema({
userName: { type: String, unique: true },
email: { type: String, unique: true },
password: String
})


// Password hash middleware.

UserSchema.pre('save', function save(next) {
const user = this
if (!user.isModified('password')) { return next() }
bcrypt.genSalt(10, (err, salt) => {
if (err) { return next(err) }
bcrypt.hash(user.password, salt, (err, hash) => {
if (err) { return next(err) }
user.password = hash
next()
})
})
})


// Helper method for validating user's password.

UserSchema.methods.comparePassword = function comparePassword(candidatePassword, cb) {
bcrypt.compare(candidatePassword, this.password, (err, isMatch) => {
cb(err, isMatch)
})
}


module.exports = mongoose.model('User', UserSchema)
Loading

0 comments on commit 344c510

Please sign in to comment.