├── .gitignore ├── src ├── public │ ├── img │ │ ├── bg.jpg │ │ └── logo.png │ ├── js │ │ └── index.js │ └── css │ │ └── style.css ├── keys.js ├── lib │ ├── handlebars.js │ ├── authentication.js │ ├── bcrypt.js │ └── passport.js ├── routes │ ├── index.js │ ├── authentication.js │ ├── contacts.js │ └── api.js ├── views │ ├── index.hbs │ ├── profile.hbs │ ├── contacts │ │ ├── add.hbs │ │ ├── edit.hbs │ │ └── list.hbs │ ├── authentication │ │ ├── signin.hbs │ │ └── signup.hbs │ ├── partials │ │ ├── message.hbs │ │ └── nav.hbs │ └── layouts │ │ └── main.hbs ├── database.js └── index.js ├── README.md ├── database └── db.sql └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /src/public/img/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antoniosarosi/Contacts-App-Nodejs/HEAD/src/public/img/bg.jpg -------------------------------------------------------------------------------- /src/public/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antoniosarosi/Contacts-App-Nodejs/HEAD/src/public/img/logo.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Contacts App 2 | 3 | ## Local Setup 4 | 5 | ```bash 6 | npm init 7 | npm run dev 8 | ``` 9 | 10 | ## Database 11 | 12 | Copy SQL from **/database/db.sql** -------------------------------------------------------------------------------- /src/keys.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | database: { 3 | host: 'localhost', 4 | user: 'antonio', 5 | password: 'admin', 6 | database: 'contacts' 7 | } 8 | }; -------------------------------------------------------------------------------- /src/lib/handlebars.js: -------------------------------------------------------------------------------- 1 | const { format } = require('timeago.js'); 2 | 3 | const helpers = {}; 4 | 5 | helpers.timeago = (timestamp) => { 6 | return format(timestamp); 7 | }; 8 | 9 | module.exports = helpers; -------------------------------------------------------------------------------- /src/routes/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | 4 | router.get('/', (req, res) => { 5 | res.render('index', { index: true }); 6 | }); 7 | 8 | module.exports = router; -------------------------------------------------------------------------------- /src/public/js/index.js: -------------------------------------------------------------------------------- 1 | document.addEventListener('DOMContentLoaded', () => { 2 | 3 | // Don't show overflow on index page 4 | function removeOverflow() { 5 | document.body.style.overflow = 'hidden'; 6 | } 7 | 8 | removeOverflow(); 9 | window.addEventListener('resize', removeOverflow); 10 | }); -------------------------------------------------------------------------------- /src/views/index.hbs: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

Contacts App

5 |

Store Your Contacts Now

6 | Get Started 7 |
8 |
9 |
-------------------------------------------------------------------------------- /src/lib/authentication.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | isAuthenticated(req, res, next) { 3 | if (req.isAuthenticated()) 4 | return next(); 5 | else 6 | return res.redirect('/signin'); 7 | }, 8 | 9 | isNotAuthenticated(req, res, next) { 10 | if (!req.isAuthenticated()) 11 | return next(); 12 | return res.redirect('/profile'); 13 | } 14 | } -------------------------------------------------------------------------------- /src/database.js: -------------------------------------------------------------------------------- 1 | const mysql = require('mysql'); 2 | const { promisify } = require('util'); 3 | 4 | const { database } = require('./keys'); 5 | 6 | const pool = mysql.createPool(database); 7 | 8 | pool.getConnection((err, connection) => { 9 | if (err) 10 | throw err; 11 | if (connection) 12 | connection.release(); 13 | console.log('DB Connected Bruh'); 14 | }); 15 | 16 | pool.query = promisify(pool.query); 17 | 18 | module.exports = pool; -------------------------------------------------------------------------------- /src/lib/bcrypt.js: -------------------------------------------------------------------------------- 1 | const bcrypt = require('bcryptjs'); 2 | 3 | encryptPassword = async (password) => { 4 | const salt = await bcrypt.genSalt(10); 5 | const hash = await bcrypt.hash(password, salt); 6 | return hash; 7 | }; 8 | 9 | comparePasswords = async (password, savedPassword) => { 10 | try { 11 | return await bcrypt.compare(password, savedPassword); 12 | } catch(err) { 13 | console.log(err); 14 | } 15 | }; 16 | 17 | module.exports = { encryptPassword, comparePasswords }; -------------------------------------------------------------------------------- /src/views/profile.hbs: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |

Welcome {{user.fullname}}

7 |

{{user.username}}

8 | 9 | See All Contacts 10 | 11 | 12 | Add New Contact 13 | 14 |
15 |
16 |
17 |
18 |
-------------------------------------------------------------------------------- /database/db.sql: -------------------------------------------------------------------------------- 1 | -- Database 2 | CREATE DATABASE contacts; 3 | USE contacts; 4 | 5 | -- Tables 6 | CREATE TABLE users ( 7 | username VARCHAR(50) PRIMARY KEY, 8 | password VARCHAR(50) NOT NULL, 9 | fullname VARCHAR(50) NOT NULL 10 | ); 11 | 12 | CREATE TABLE contacts ( 13 | id INT(12) PRIMARY KEY AUTO_INCREMENT, 14 | name VARCHAR(50) NOT NULL, 15 | number VARCHAR(9) NOT NULL, 16 | user VARCHAR(50) NOT NULL, 17 | created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, 18 | CONSTRAINT fk_user FOREIGN KEY(user) REFERENCES users(username) 19 | ); 20 | 21 | -- Test User 22 | INSERT INTO users(username, password, fullname) VALUES ( 23 | 'test', 'test', 'Test User' 24 | ); 25 | 26 | -------------------------------------------------------------------------------- /src/views/contacts/add.hbs: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | 9 |
10 |
11 | 12 |
13 |
14 | 17 |
18 |
19 |
20 |
21 |
22 |
23 |
-------------------------------------------------------------------------------- /src/views/contacts/edit.hbs: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | 9 |
10 |
11 | 12 |
13 |
14 | 17 |
18 |
19 |
20 |
21 |
22 |
23 |
-------------------------------------------------------------------------------- /src/views/authentication/signin.hbs: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |

Sign In

7 |
8 |
9 |
10 |
11 | 12 |
13 |
14 | 15 |
16 |
17 | 20 |
21 |
22 |
23 |
24 |
25 |
26 |
-------------------------------------------------------------------------------- /src/views/partials/message.hbs: -------------------------------------------------------------------------------- 1 | {{#if success}} 2 |
3 |
4 |
5 | 11 |
12 |
13 |
14 | {{/if}} 15 | 16 | {{#if message}} 17 |
18 |
19 |
20 | 26 |
27 |
28 |
29 | {{/if}} -------------------------------------------------------------------------------- /src/views/contacts/list.hbs: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{#each contacts}} 4 |
5 |
6 |
7 |

8 | {{name}} 9 |

10 |

{{number}}

11 |

{{timeago created_at}}

12 | Edit Contact 13 | Delete Contact 14 |
15 |
16 |
17 | {{else}} 18 |
19 |
20 |

No contacts saved yet

21 | Add One! 22 |
23 |
24 | {{/each}} 25 |
26 |
-------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "contacts-app", 3 | "version": "1.0.0", 4 | "description": "contacts app", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "nodemon src/" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/antoniosarosi/Contacts-App-Nodejs.git" 12 | }, 13 | "keywords": [ 14 | "Nodejs" 15 | ], 16 | "author": "Antonio Sarosi", 17 | "license": "ISC", 18 | "bugs": { 19 | "url": "https://github.com/antoniosarosi/Contacts-App-Nodejs/issues" 20 | }, 21 | "homepage": "https://github.com/antoniosarosi/Contacts-App-Nodejs#readme", 22 | "devDependencies": { 23 | "nodemon": "^1.19.3" 24 | }, 25 | "dependencies": { 26 | "bcryptjs": "^2.4.3", 27 | "connect-flash": "^0.1.1", 28 | "express": "^4.17.1", 29 | "express-handlebars": "^3.1.0", 30 | "express-mysql-session": "^2.1.0", 31 | "express-session": "^1.16.2", 32 | "morgan": "^1.9.1", 33 | "mysql": "^2.17.1", 34 | "passport": "^0.4.0", 35 | "passport-local": "^1.0.0", 36 | "timeago.js": "^4.0.0-beta.2" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/views/authentication/signup.hbs: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |

Sign Up

7 |
8 |
9 |
10 |
11 | 12 |
13 |
14 | 15 |
16 |
17 | 18 |
19 |
20 | 23 |
24 |
25 |
26 |
27 |
28 |
29 |
-------------------------------------------------------------------------------- /src/public/css/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: 'Quicksand', sans-serif; 3 | background: #22242e; 4 | } 5 | 6 | /* Index */ 7 | 8 | .index { 9 | width: 100vw; 10 | height: 100vh; 11 | } 12 | 13 | .masthead { 14 | color: #fff; 15 | width: 100%; 16 | height: 100%; 17 | background: url('/img/bg.jpg'); 18 | background-position: center center; 19 | background-repeat: no-repeat; 20 | background-size: cover; 21 | } 22 | 23 | .btn-primary.start-button { 24 | background: #11141d; 25 | color: #fff; 26 | } 27 | 28 | /* Nav */ 29 | 30 | .navbar { 31 | background: #11141d; 32 | } 33 | 34 | .navbar img { 35 | width: 23px; 36 | } 37 | 38 | /* Cards */ 39 | 40 | .card { 41 | background: #11141d; 42 | color: #fff; 43 | } 44 | 45 | .form-control { 46 | background: #373b3d; 47 | border: none; 48 | color: #fff; 49 | } 50 | 51 | .btn-primary { 52 | background: linear-gradient(to right, #6bbf47, #3e863d); 53 | border: none; 54 | border-radius: 5px; 55 | height: 35px; 56 | color: #333; 57 | font-weight: bold; 58 | transition: ease-in-out .2s; 59 | } 60 | 61 | .btn-primary:hover { 62 | transform: scale(1.05); 63 | } 64 | -------------------------------------------------------------------------------- /src/routes/authentication.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | const passport = require('passport'); 4 | 5 | const { isAuthenticated, isNotAuthenticated } = require('../lib/authentication'); 6 | 7 | // Sign Up 8 | router.get('/signup', isNotAuthenticated, (req, res) => { 9 | res.render('authentication/signup'); 10 | }); 11 | 12 | router.post('/signup', passport.authenticate('local.signup', { 13 | successRedirect: '/profile', 14 | failureRedirect: '/signup', 15 | failureFlash: true 16 | })); 17 | 18 | // Sign In 19 | router.get('/signin', (req, res) => { 20 | res.render('authentication/signin'); 21 | }); 22 | 23 | router.post('/signin', (req, res, next) => { 24 | passport.authenticate('local.signin', { 25 | successRedirect: '/profile', 26 | failureRedirect: '/signin', 27 | failureFlash: true 28 | })(req, res, next); 29 | }); 30 | 31 | // Profile 32 | router.get('/profile', isAuthenticated, (req, res) => { 33 | res.render('profile'); 34 | }); 35 | 36 | // Log Out 37 | router.get('/logout', isAuthenticated, (req, res) => { 38 | req.logOut(); 39 | res.redirect('signin'); 40 | }) 41 | 42 | module.exports = router; 43 | 44 | -------------------------------------------------------------------------------- /src/lib/passport.js: -------------------------------------------------------------------------------- 1 | const passport = require('passport'); 2 | const Strategy = require('passport-local').Strategy; 3 | 4 | const pool = require('../database'); 5 | const bcrypt = require('../lib/bcrypt') ; 6 | 7 | passport.use('local.signin', new Strategy({ 8 | usernameField: 'username', 9 | passwordField: 'password', 10 | passReqToCallback: true 11 | }, async (req, username, password, done) => { 12 | const rows = await pool.query('SELECT * FROM users WHERE username = ?', [username]); 13 | if (rows.length > 0) { 14 | const user = rows[0]; 15 | const validPassword = await bcrypt.comparePasswords(password, user.password); 16 | if (validPassword) 17 | done(null, user); 18 | else 19 | done(null, false, req.flash('message', 'Incorrect Password')); 20 | } 21 | else 22 | done(null, false, req.flash('message', 'Incorrect Username')); 23 | })); 24 | 25 | passport.use('local.signup', new Strategy({ 26 | usernameField: 'username', 27 | passwordField: 'password', 28 | passReqToCallback: true 29 | }, async (req, username, password, done) => { 30 | const user = { username, fullname: req.body.fullname }; 31 | user.password = await bcrypt.encryptPassword(password); 32 | await pool.query('INSERT INTO users SET ?', [user]); 33 | done(null, user); 34 | })); 35 | 36 | passport.serializeUser((user, done) => { 37 | console.log("Serialize: "); 38 | console.log(user); 39 | done(null, user.username); 40 | }); 41 | 42 | passport.deserializeUser(async (id, done) => { 43 | const user = await pool.query('SELECT * FROM users WHERE username = ?', [id]); 44 | done(null, user[0]); 45 | }); -------------------------------------------------------------------------------- /src/views/layouts/main.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {{!-- Bootsrap --}} 9 | 15 | 16 | 17 | 18 | {{!-- Google Fonts --}} 19 | 23 | 24 | Contacts App 25 | 26 | 27 | 28 | {{> nav}} 29 | 30 | {{> message}} 31 | 32 | {{{ body }}} 33 | 34 | {{!-- Bootsrap --}} 35 | 40 | 45 | 50 | 51 | {{#if index}} 52 | 53 | {{/if}} 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/routes/contacts.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | const pool = require('../database'); 4 | 5 | const { isAuthenticated } = require('../lib/authentication'); 6 | 7 | 8 | router.get('/contacts', isAuthenticated, async (req, res) => { 9 | console.log(req.user); 10 | const contacts = await pool.query('SELECT * FROM contacts WHERE user = ?', [req.user.username]); 11 | console.log(contacts); 12 | res.render('contacts/list', {contacts}); 13 | }); 14 | 15 | // Add new contact 16 | 17 | router.get('/contacts/add', isAuthenticated, (req, res) => { 18 | res.render('contacts/add'); 19 | }); 20 | 21 | router.post('/contacts/add', async (req, res) => { 22 | const contact = req.body; 23 | contact.user = req.user.username; 24 | await pool.query('INSERT INTO contacts SET ?', [contact]); 25 | req.flash('success', 'Contact Added Successfully'); 26 | res.redirect('/contacts'); 27 | }); 28 | 29 | // Delete contact 30 | 31 | router.get('/contacts/delete/:id', isAuthenticated, async (req, res) => { 32 | const { id } = req.params; 33 | await pool.query('DELETE FROM contacts WHERE id = ?', [id]); 34 | req.flash('success', 'Contact Deleted Successfully'); 35 | res.redirect('/contacts'); 36 | }); 37 | 38 | // Edit contact 39 | 40 | router.get('/contacts/edit/:id', isAuthenticated, async (req, res) => { 41 | const { id } = req.params; 42 | const contact = await pool.query('SELECT * FROM contacts WHERE id = ?', [id]); 43 | res.render('contacts/edit', { contact: contact[0] }); 44 | }); 45 | 46 | router.post('/contacts/edit/:id', async (req, res) => { 47 | const { id } = req.params; 48 | await pool.query('UPDATE contacts SET ? WHERE id = ?', [req.body, id]); 49 | req.flash('success', 'Contact Updated Successfully'); 50 | res.redirect('/contacts'); 51 | }); 52 | 53 | module.exports = router; 54 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const express = require('express'); 3 | const morgan = require('morgan'); 4 | const handlebars = require('express-handlebars'); 5 | const session = require('express-session'); 6 | const mysqlSession = require('express-mysql-session'); 7 | const passport = require('passport'); 8 | const flash = require('connect-flash'); 9 | 10 | const { database } = require('./keys'); 11 | require('./lib/passport'); 12 | 13 | // Initializations 14 | const app = express(); 15 | 16 | // Settings 17 | app.set('port', process.env.PORT || 4000); 18 | app.set('views', path.join(__dirname, 'views')); 19 | app.engine('.hbs', handlebars({ 20 | defaultLayout: 'main', 21 | layoutsDir: path.join(app.get('views'), 'layouts'), 22 | partialsDir: path.join(app.get('views'), 'partials'), 23 | extname: '.hbs', 24 | helpers: require('./lib/handlebars') 25 | })); 26 | app.set('view engine', '.hbs'); 27 | 28 | // Middlewares 29 | app.use(morgan('dev')); 30 | app.use(express.json()); 31 | app.use(express.urlencoded({ extended: false })); 32 | app.use(session({ 33 | secret: 'contactsapp', 34 | resave: false, 35 | saveUninitialized: false, 36 | store: mysqlSession(database) 37 | })); 38 | app.use(passport.initialize()); 39 | app.use(passport.session()); 40 | app.use(flash()); 41 | 42 | // Global 43 | app.use((req, res, next) => { 44 | app.locals.success = req.flash('success'); 45 | app.locals.message = req.flash('message'); 46 | app.locals.user = req.user; 47 | next(); 48 | }); 49 | 50 | // Routes 51 | app.use(require('./routes/')); 52 | app.use(require('./routes/contacts')); 53 | app.use(require('./routes/authentication')); 54 | app.use(require('./routes/api')); 55 | 56 | // Public 57 | app.use(express.static(path.join(__dirname, 'public'))); 58 | 59 | // Server 60 | app.listen(app.get('port'), () => { 61 | console.log('Server listening on port', app.get('port')); 62 | }); -------------------------------------------------------------------------------- /src/views/partials/nav.hbs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/routes/api.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | const pool = require('../database'); 4 | 5 | // Get all users 6 | router.get('/api/users', async (req, res) => { 7 | const users = await pool.query('SELECT * FROM users'); 8 | res.json(users); 9 | }); 10 | 11 | // Get one user 12 | router.get('/api/users/:username', async (req, res) => { 13 | const { username } = req.params; 14 | const user = await pool.query('SELECT * FROM users WHERE username = ?', [username]) 15 | res.json(user); 16 | }); 17 | 18 | // Create user 19 | router.post('/api/users', async (req, res) => { 20 | try { 21 | // Password should be encypted before inserting user 22 | await pool.query('INSERT INTO users SET ?', [req.body]); 23 | res.json({status: 'created'}); 24 | } catch(err) { 25 | console.log(err); 26 | res.json({status: 'error'}); 27 | } 28 | }); 29 | 30 | // Update user 31 | router.put('/api/users/:username', async (req, res) => { 32 | try { 33 | const { username } = req.params; 34 | await pool.query('UPDATE users SET ? WHERE username = ?', [req.body, username]); 35 | res.json({status: 'updated'}); 36 | } catch(err) { 37 | res.json({status: 'error'}); 38 | } 39 | }); 40 | 41 | // Delete user 42 | router.delete('/api/users/:username', async (req, res) => { 43 | try { 44 | const { username } = req.params; 45 | await pool.query('DELETE FROM users WHERE username = ?', username); 46 | res.json({status: 'deleted'}); 47 | } catch(err) { 48 | res.json({status: 'error'}); 49 | } 50 | }); 51 | 52 | // Get all contacts 53 | router.get('/api/contacts', async (req, res) => { 54 | const contacts = await pool.query('SELECT * FROM contacts'); 55 | res.json(contacts); 56 | }); 57 | 58 | // Get one contact 59 | router.get('/api/contacts/:id', async (req, res) => { 60 | const { id } = req.params; 61 | const contact = await pool.query('SELECT * FROM contacts WHERE id = ?', [id]) 62 | res.json(contact); 63 | }); 64 | 65 | // Create contact 66 | router.post('/api/contacts', async (req, res) => { 67 | try { 68 | await pool.query('INSERT INTO contacts SET ?', [req.body]); 69 | res.json({status: 'created'}); 70 | } catch(err) { 71 | console.log(err); 72 | res.json({status: 'error'}); 73 | } 74 | }); 75 | 76 | // Update contact 77 | router.put('/api/contacts/:id', async (req, res) => { 78 | try { 79 | const { id } = req.params; 80 | await pool.query('UPDATE contacts SET ? WHERE id = ?', [req.body, id]); 81 | res.json({status: 'updated'}); 82 | } catch(err) { 83 | res.json({status: 'error'}); 84 | } 85 | }); 86 | 87 | // Delete Contact 88 | router.delete('/api/contacts/:id', async (req, res) => { 89 | try { 90 | const { id } = req.params; 91 | await pool.query('DELETE FROM contacts WHERE id = ?', id); 92 | res.json({status: 'deleted'}); 93 | } catch(err) { 94 | res.json({status: 'error'}); 95 | } 96 | }); 97 | 98 | // Get contacts for specific user 99 | router.get('/api/contacts/user/:username', async (req, res) => { 100 | const { username } = req.params; 101 | const users = await pool.query('SELECT * FROM contacts WHERE user = ?', [username]); 102 | res.json(users); 103 | }); 104 | 105 | module.exports = router; --------------------------------------------------------------------------------