├── .gitignore ├── config ├── monkoKEY.js └── googleData.js ├── .gitattributes ├── static └── images │ ├── logo.png │ ├── link_shorter.png │ └── link_shorter.svg ├── screenshots ├── dashboard.png └── shortly_main_page.png ├── views ├── partials │ └── footer.ejs ├── success.ejs ├── signup.ejs ├── login.ejs ├── index.ejs ├── forgot-password.ejs └── dashboard.ejs ├── model ├── user.js ├── resetTokens.js └── url.js ├── package.json ├── README.md ├── controller ├── sendMail.js ├── passportLocal.js ├── googleAuth.js ├── routes.js └── accountRoutes.js └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | node_modules -------------------------------------------------------------------------------- /config/monkoKEY.js: -------------------------------------------------------------------------------- 1 | module.exports = ""; -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * linguist-vendored 2 | *.js linguist-vendored=false 3 | -------------------------------------------------------------------------------- /static/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desi-programmer/shortly-nodejs/HEAD/static/images/logo.png -------------------------------------------------------------------------------- /screenshots/dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desi-programmer/shortly-nodejs/HEAD/screenshots/dashboard.png -------------------------------------------------------------------------------- /static/images/link_shorter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desi-programmer/shortly-nodejs/HEAD/static/images/link_shorter.png -------------------------------------------------------------------------------- /config/googleData.js: -------------------------------------------------------------------------------- 1 | module.exports.clientId = ""; 2 | module.exports.clientSecret = ""; -------------------------------------------------------------------------------- /screenshots/shortly_main_page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desi-programmer/shortly-nodejs/HEAD/screenshots/shortly_main_page.png -------------------------------------------------------------------------------- /views/partials/footer.ejs: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

Learn More at Desi Programmer

5 |
6 |
7 |
-------------------------------------------------------------------------------- /model/user.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const userSchema = new mongoose.Schema({ 4 | 5 | email: { 6 | type: String, 7 | required: true, 8 | }, 9 | password: { 10 | type: String, 11 | }, 12 | 13 | isVerified: { 14 | type: Boolean, 15 | default: false, 16 | }, 17 | 18 | googleId: { 19 | type: String, 20 | }, 21 | provider: { 22 | type: String, 23 | required: true, 24 | } 25 | }); 26 | 27 | module.exports = mongoose.model('user', userSchema); -------------------------------------------------------------------------------- /model/resetTokens.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const resetTokens = new mongoose.Schema({ 4 | token: { 5 | type: String, 6 | required: true, 7 | }, 8 | email: { 9 | type: String, 10 | required: true, 11 | }, 12 | created: { 13 | type: Date, 14 | default: () => Date.now(), 15 | }, 16 | // will automatically delete after 10 min 17 | // can be a bit delay, because the bg thread runs every 60 sec 18 | expire_at: { type: Date, default: Date.now, expires: 600 } 19 | }); 20 | 21 | module.exports = mongoose.model('resetTokens', resetTokens); -------------------------------------------------------------------------------- /model/url.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const urlSchema = new mongoose.Schema({ 4 | owned : { 5 | type : String, 6 | required : true, 7 | }, 8 | originalUrl : { 9 | type : String, 10 | required : true, 11 | }, 12 | slug : { 13 | type : String, 14 | unique : true, 15 | required : true, 16 | }, 17 | visits : { 18 | type : Number, 19 | default : 0, 20 | }, 21 | visitsFB : { 22 | type : Number, 23 | default : 0, 24 | }, 25 | visitsIG : { 26 | type : Number, 27 | default : 0, 28 | }, 29 | visitsYT : { 30 | type : Number, 31 | default : 0, 32 | }, 33 | 34 | }); 35 | 36 | module.exports = mongoose.model('url', urlSchema); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shortly", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "nodemon index", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "bcryptjs": "^2.4.3", 15 | "connect-flash": "^0.1.1", 16 | "cookie-parser": "^1.4.5", 17 | "csurf": "^1.11.0", 18 | "ejs": "^3.1.6", 19 | "express": "^4.17.1", 20 | "express-session": "^1.17.1", 21 | "memorystore": "^1.6.5", 22 | "mongoose": "^5.11.18", 23 | "nodemailer": "^6.5.0", 24 | "passport": "^0.4.1", 25 | "passport-google-oauth20": "^2.0.0", 26 | "passport-local": "^1.0.0" 27 | }, 28 | "devDependencies": { 29 | "nodemon": "^2.0.7" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Shortly - A Short Url Service In Node JS With Data Visualization 2 | 3 | This repo contains code for a Short URl Service Coded In NODE JS ! 4 | 5 | ## Tutorial (Shortly) : [Here](https://youtu.be/zuXQH-PDWuY) 6 | 7 | ## Tutorial (Local Auth) : [Here](https://www.youtube.com/watch?v=-ZxXS9gsWX4) 8 | 9 | ## Tutorial (Google Auth) : [Here](https://www.youtube.com/watch?v=d-IToO3gLrM) 10 | 11 | ## 1. USAGE 12 | 13 | ### Install All Packages 14 | 15 | ```bash 16 | npm install express ejs mongoose bcryptjs connect-flash cookie-parser express-session csurf memorystore passport passport-local passport-google-oauth20 nodemailer 17 | ``` 18 | 19 | ### Install Nodemon For Development 20 | 21 | ```bash 22 | npm install -D nodemon 23 | ``` 24 | 25 | ### Add mongoURI ,Google client ID and Secret, smtp config for sending emails 26 | ### And Feel free to delete the screenshots directory 27 | 28 | # Added Options 29 | 30 | ![Image1](screenshots/shortly_main_page.png) 31 | ![Image2](screenshots/dashboard.png) 32 | 33 | 34 | -------------------------------------------------------------------------------- /controller/sendMail.js: -------------------------------------------------------------------------------- 1 | const nodemailer = require("nodemailer"); 2 | 3 | var smtpTransport = nodemailer.createTransport({ 4 | service: "gmail", 5 | auth: { 6 | user: "", 7 | pass: "", 8 | }, 9 | }); 10 | 11 | module.exports.sendResetEmail = async (email, token) => { 12 | // change first part to your domain 13 | var url = "http://localhost:8000/user/reset-password?token=" + token; 14 | 15 | await smtpTransport.sendMail({ 16 | from: "", 17 | to: email, 18 | subject: "RESET YOUR PASSWORD", 19 | text: `Click on this link to reset your password ${url}`, 20 | html: `

Click on this link to reset your password : ${url}

`, 21 | }); 22 | }; 23 | 24 | module.exports.sendVerifyEmail = async (email, token) => { 25 | // change first part to your domain 26 | var url = "http://localhost:8000/user/verifyemail?token=" + token; 27 | 28 | await smtpTransport.sendMail({ 29 | from: "", 30 | to: email, 31 | subject: "VERIFY Your EMAIL", 32 | text: `Click on this link to verify ${url}`, 33 | html: `

Click on this link to verify your email : ${url}

`, 34 | }); 35 | }; 36 | -------------------------------------------------------------------------------- /controller/passportLocal.js: -------------------------------------------------------------------------------- 1 | const user = require('../model/user'); 2 | const bcryptjs = require('bcryptjs'); 3 | var localStrategy = require('passport-local').Strategy; 4 | 5 | module.exports = function (passport) { 6 | passport.use(new localStrategy({ usernameField: 'email' }, (email, password, done) => { 7 | user.findOne({ email: email }, (err, data) => { 8 | if (err) throw err; 9 | if (!data) { 10 | return done(null, false, { message: "User Doesn't Exist !" }); 11 | } 12 | bcryptjs.compare(password, data.password, (err, match) => { 13 | if (err) { 14 | return done(null, false); 15 | } 16 | if (!match) { 17 | return done(null, false, { message: "Password Doesn't match !" }); 18 | } 19 | if (match) { 20 | return done(null, data); 21 | } 22 | }) 23 | }) 24 | })); 25 | 26 | passport.serializeUser(function (user, done) { 27 | done(null, user.id); 28 | }); 29 | 30 | passport.deserializeUser(function (id, done) { 31 | user.findById(id, function (err, user) { 32 | done(err, user); 33 | }); 34 | }); 35 | 36 | } -------------------------------------------------------------------------------- /views/success.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Success 10 | 11 | 12 | 13 |
14 |

Welcome <%= user.username %>

15 |

If You See This Page
Then You Have Logged In Successfully !

16 | Logout 17 |
18 |
19 |
20 | 22 |
23 |
24 | 25 |
26 |
27 | <% user.messages.forEach(function(msg, index){ %> 28 |

<%= msg %>

29 | <% }) %> 30 |
31 |
32 |

33 |

34 |
35 |
36 | 37 | 38 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const mongoose = require('mongoose'); 3 | const csrf = require('csurf'); 4 | const cookieParser = require('cookie-parser'); 5 | const expressSession = require('express-session'); 6 | var MemoryStore = require('memorystore')(expressSession) 7 | const passport = require('passport'); 8 | const flash = require('connect-flash'); 9 | 10 | const app = express(); 11 | 12 | app.set('view engine', 'ejs'); 13 | app.set('views', __dirname + '/views',); 14 | 15 | app.use('/static', express.static(__dirname + '/static')); 16 | 17 | app.use(express.urlencoded({ extended: true })); 18 | 19 | const mongoURI = require('./config/monkoKEY'); 20 | mongoose.connect(mongoURI, { useNewUrlParser: true, useUnifiedTopology: true, useFindAndModify: false, useCreateIndex: true, },).then(() => console.log("Connected !"),); 21 | 22 | app.use(cookieParser('random')); 23 | 24 | app.use(expressSession({ 25 | secret: "random", 26 | resave: true, 27 | saveUninitialized: true, 28 | // setting the max age to longer duration 29 | maxAge: 24 * 60 * 60 * 1000, 30 | store: new MemoryStore(), 31 | })); 32 | 33 | app.use(csrf()); 34 | app.use(passport.initialize()); 35 | app.use(passport.session()); 36 | 37 | app.use(flash()); 38 | 39 | app.use(function (req, res, next) { 40 | res.locals.success_messages = req.flash('success_messages'); 41 | res.locals.error_messages = req.flash('error_messages'); 42 | res.locals.error = req.flash('error'); 43 | next(); 44 | }); 45 | 46 | app.use(require('./controller/routes.js')); 47 | 48 | const PORT = process.env.PORT || 8000; 49 | 50 | app.listen(PORT, () => console.log("Server Started At " + PORT)); -------------------------------------------------------------------------------- /controller/googleAuth.js: -------------------------------------------------------------------------------- 1 | var GoogleStrategy = require('passport-google-oauth20').Strategy; 2 | const user = require('../model/user'); 3 | const clientId = require('../config/googleData').clientId; 4 | const clientSecreT = require('../config/googleData').clientSecret; 5 | 6 | module.exports = function (passport) { 7 | passport.use(new GoogleStrategy({ 8 | clientID: clientId, 9 | clientSecret: clientSecreT, 10 | callbackURL: "http://localhost:8000/google/callback" 11 | }, (accessToken, refreshToken, profile, done) => { 12 | console.log(profile.emails[0].value); 13 | 14 | // find if a user exist with this email or not 15 | user.findOne({ email: profile.emails[0].value }).then((data) => { 16 | if (data) { 17 | // user exists 18 | // update data 19 | // I am skipping that part here, may Update Later 20 | return done(null, data); 21 | } else { 22 | // create a user 23 | user({ 24 | 25 | email: profile.emails[0].value, 26 | googleId: profile.id, 27 | password: null, 28 | provider: 'google', 29 | isVerified: true, 30 | }).save(function (err, data) { 31 | return done(null, data); 32 | }); 33 | } 34 | }); 35 | } 36 | )); 37 | passport.serializeUser(function (user, done) { 38 | done(null, user.id); 39 | }); 40 | 41 | passport.deserializeUser(function (id, done) { 42 | user.findById(id, function (err, user) { 43 | done(err, user); 44 | }); 45 | }); 46 | 47 | } -------------------------------------------------------------------------------- /static/images/link_shorter.svg: -------------------------------------------------------------------------------- 1 | link -------------------------------------------------------------------------------- /views/signup.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 14 | Shortly 15 | 26 | 27 | 28 | 29 | 30 | 31 | 56 | 57 | 58 | 59 |
60 |

Register Yourself

61 |
62 | 63 | <% if(typeof err !='undefined' ){ %> 64 | 69 | <% } %> 70 | 71 |
72 | 73 |
74 | 75 | 77 |
78 |
79 | 80 | 82 |
83 |
84 | 85 | 87 |
88 |
89 | 90 | Continue With Google 91 |
92 |

Already Registered ? LOGIN Here

93 |
94 |
95 |
96 | 97 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /views/login.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 13 | 14 | 20 | Shortly 21 | 32 | 33 | 34 | 35 | 36 | 61 | 62 | 63 |
64 |

Login Here

65 |
69 | <% if( error !="" ){ %> 70 | 83 | <% } %> 84 | 85 | 86 | 87 | 88 | <% if( error_messages !="" ){ %> 89 | 94 | <% } %> 95 | 96 |
97 | 98 |
99 | 100 | 108 |
109 |
110 | 111 | 119 |
120 |
121 | 126 | Continue With Google 129 |
130 |

New Here ? Register Now

131 | 132 |

133 | Forgot Password 136 |

137 |
138 |
139 |
140 | 141 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 13 | 14 | 20 | 21 | 25 | 26 | Shortly 27 | 28 | 37 | 38 | 39 | 40 | 85 | 86 | 87 |
88 |
89 |
90 | <% if(typeof err !='undefined' ){ %> <% if(err){ %> 91 | 104 | 105 | <% } %> <% } %> 106 | 107 | Banner 112 |

Shorten Any URL

113 |

And Get deep Analytics from them !

114 | 115 | Start Creating Now 116 |
117 |
118 |
119 | 120 |
121 |
122 |
123 |
124 | 125 |
126 |

Easy Analytics

127 |

Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam esse debitis, iure nobis quasi magnam magni facere ad commodi iste.

128 | 129 |
130 |
131 |
132 |
133 |
134 | 135 |
136 |

Fast And Powerful

137 |

Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam esse debitis, iure nobis quasi magnam magni facere ad commodi iste.

138 | 139 |
140 |
141 |
142 |
143 |
144 | 145 |
146 |

Who Drives Traffic

147 |

Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam esse debitis, iure nobis quasi magnam magni facere ad commodi iste.

148 | 149 |
150 |
151 |
152 |
153 |
154 | 155 | <%- include('./partials/footer.ejs'); %> 156 | 157 | 162 | 163 | 164 | -------------------------------------------------------------------------------- /views/forgot-password.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 13 | 14 | 20 | Authentications 21 | 32 | 33 | 34 | 35 | 36 | 65 | 66 | 67 |
68 |

Reset Your Password

69 |
73 | <% if(typeof reset !='undefined' ){ %> <% if( reset){ %> <% if(typeof 74 | err !='undefined' ){ %> <% if( err !="" ){ %> 75 | 88 | <% } %> <% } %> 89 | 90 | 91 |
92 | 93 |
94 | 95 | 103 |
104 |
105 | 106 | 114 |
115 | 116 |
117 | 122 |
123 |
124 | 125 | <% }else{ %> <% } %> <% }else{ %> 126 | 127 | 128 | <% if(typeof msg !='undefined' ){ %> <% if( msg !="" ){ %> 129 | 142 | <% } %> <% } %> 143 | 144 |
145 | 146 |
147 | 148 | 156 |
157 | 158 |
159 | 164 |
165 |
166 | <% } %> 167 |
168 |
169 | 170 | 175 | 176 | 177 | -------------------------------------------------------------------------------- /controller/routes.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | const user = require('../model/user'); 4 | const urls = require('../model/url'); 5 | const bcryptjs = require('bcryptjs'); 6 | const passport = require('passport'); 7 | require('./passportLocal')(passport); 8 | require('./googleAuth')(passport); 9 | const userRoutes = require('./accountRoutes'); 10 | 11 | function checkAuth(req, res, next) { 12 | if (req.isAuthenticated()) { 13 | res.set('Cache-Control', 'no-cache, private, no-store, must-revalidate, post-check=0, pre-check=0'); 14 | next(); 15 | } else { 16 | req.flash('error_messages', "Please Login to continue !"); 17 | res.redirect('/login'); 18 | } 19 | } 20 | 21 | 22 | router.get('/login', (req, res) => { 23 | res.render("login", { csrfToken: req.csrfToken() }); 24 | }); 25 | 26 | router.get('/signup', (req, res) => { 27 | res.render("signup", { csrfToken: req.csrfToken() }); 28 | }); 29 | 30 | router.post('/signup', (req, res) => { 31 | // get all the values 32 | const { email, password, confirmpassword } = req.body; 33 | // check if the are empty 34 | if (!email || !password || !confirmpassword) { 35 | res.render("signup", { err: "All Fields Required !", csrfToken: req.csrfToken() }); 36 | } else if (password != confirmpassword) { 37 | res.render("signup", { err: "Password Don't Match !", csrfToken: req.csrfToken() }); 38 | } else { 39 | 40 | // validate email and username and password 41 | // skipping validation 42 | // check if a user exists 43 | user.findOne({ email: email }, function (err, data) { 44 | if (err) throw err; 45 | if (data) { 46 | res.render("signup", { err: "User Exists, Try Logging In !", csrfToken: req.csrfToken() }); 47 | } else { 48 | // generate a salt 49 | bcryptjs.genSalt(12, (err, salt) => { 50 | if (err) throw err; 51 | // hash the password 52 | bcryptjs.hash(password, salt, (err, hash) => { 53 | if (err) throw err; 54 | // save user in db 55 | user({ 56 | email: email, 57 | password: hash, 58 | googleId: null, 59 | provider: 'email', 60 | }).save((err, data) => { 61 | if (err) throw err; 62 | // login the user 63 | // use req.login 64 | // redirect , if you don't want to login 65 | res.redirect('/login'); 66 | }); 67 | }) 68 | }); 69 | } 70 | }); 71 | } 72 | }); 73 | 74 | router.post('/login', (req, res, next) => { 75 | passport.authenticate('local', { 76 | failureRedirect: '/login', 77 | successRedirect: '/dashboard', 78 | failureFlash: true, 79 | })(req, res, next); 80 | }); 81 | 82 | router.get('/logout', (req, res) => { 83 | req.logout(); 84 | req.session.destroy(function (err) { 85 | res.redirect('/'); 86 | }); 87 | }); 88 | 89 | router.get('/google', passport.authenticate('google', { scope: ['profile', 'email',] })); 90 | 91 | router.get('/google/callback', passport.authenticate('google', { failureRedirect: '/login' }), (req, res) => { 92 | res.redirect('/dashboard'); 93 | }); 94 | 95 | router.get('/dashboard', checkAuth, (req, res) => { 96 | urls.find({ owned : req.user.email }, (err, data) => { 97 | if(err) throw err; 98 | res.render('dashboard', { verified: req.user.isVerified, logged: true, csrfToken: req.csrfToken(), urls : data }); 99 | 100 | }); 101 | }); 102 | 103 | 104 | router.post('/create', checkAuth, (req, res) => { 105 | const { original, short } = req.body; 106 | 107 | if (!original || !short) { 108 | 109 | res.render('dashboard', { verified: req.user.isVerified, logged: true, csrfToken: req.csrfToken(), err: "Empty Fields !" }); 110 | } else { 111 | urls.findOne({ slug: short }, (err, data) => { 112 | if (err) throw err; 113 | if (data) { 114 | res.render('dashboard', { verified: req.user.isVerified, logged: true, csrfToken: req.csrfToken(), err: "Try Different Short Url, This exists !" }); 115 | 116 | } else { 117 | urls({ 118 | originalUrl: original, 119 | slug: short, 120 | owned: req.user.email, 121 | }).save((err) => { 122 | res.redirect('/dashboard'); 123 | }); 124 | } 125 | }) 126 | } 127 | 128 | }); 129 | 130 | router.use(userRoutes); 131 | 132 | 133 | router.get('/:slug?', async (req, res) => { 134 | 135 | if (req.params.slug != undefined) { 136 | var data = await urls.findOne({ slug: req.params.slug }); 137 | if (data) { 138 | data.visits = data.visits + 1; 139 | 140 | var ref = req.query.ref; 141 | if (ref) { 142 | switch (ref) { 143 | case 'fb': 144 | data.visitsFB = data.visitsFB + 1; 145 | break; 146 | case 'ig': 147 | data.visitsIG = data.visitsIG + 1; 148 | break; 149 | case 'yt': 150 | data.visitsYT = data.visitsYT + 1; 151 | break; 152 | } 153 | } 154 | 155 | await data.save(); 156 | 157 | res.redirect(data.originalUrl); 158 | } else { 159 | if (req.isAuthenticated()) { 160 | res.render("index", { logged: true, err: true }); 161 | } else { 162 | res.render("index", { logged: false, err: true }); 163 | } 164 | 165 | } 166 | 167 | 168 | } else { 169 | if (req.isAuthenticated()) { 170 | res.render("index", { logged: true }); 171 | } else { 172 | res.render("index", { logged: false }); 173 | } 174 | } 175 | 176 | }); 177 | 178 | 179 | 180 | module.exports = router; -------------------------------------------------------------------------------- /controller/accountRoutes.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | const crypto = require('crypto'); 4 | const resetToken = require('../model/resetTokens'); 5 | const user = require('../model/user'); 6 | const mailer = require('./sendMail'); 7 | const bcryptjs = require('bcryptjs'); 8 | 9 | function checkAuth(req, res, next) { 10 | if (req.isAuthenticated()) { 11 | res.set('Cache-Control', 'no-cache, private, no-store, must-revalidate, post-check=0, pre-check=0'); 12 | next(); 13 | } else { 14 | req.flash('error_messages', "Please Login to continue !"); 15 | res.redirect('/login'); 16 | } 17 | } 18 | 19 | // adding the checkAuth middleware to make sure that 20 | // only authenticated users can send emails 21 | router.get('/user/send-verification-email', checkAuth, async (req, res) => { 22 | // check if user is google or already verified 23 | if (req.user.isVerified || req.user.provider == 'google') { 24 | // already verified or google user 25 | // since we won't show any such option in the UI 26 | // most probably this is being called by mistake or can be an attack 27 | // simply redirect to profile 28 | res.redirect('/dashboard'); 29 | } else { 30 | // generate a token 31 | var token = crypto.randomBytes(32).toString('hex'); 32 | // add that to database 33 | await resetToken({ token: token, email: req.user.email }).save(); 34 | // send an email for verification 35 | mailer.sendVerifyEmail(req.user.email, token); 36 | res.render('profile', { username: req.user.username, verified: req.user.isVerified, emailsent: true }); 37 | } 38 | }); 39 | 40 | 41 | router.get('/user/verifyemail', async (req, res) => { 42 | // grab the token 43 | const token = req.query.token; 44 | // check if token exists 45 | // or just send an error 46 | if (token) { 47 | var check = await resetToken.findOne({ token: token }); 48 | if (check) { 49 | // token verified 50 | // set the property of verified to true for the user 51 | var userData = await user.findOne({ email: check.email }); 52 | userData.isVerified = true; 53 | await userData.save(); 54 | // delete the token now itself 55 | await resetToken.findOneAndDelete({ token: token }); 56 | res.redirect('/dashboard'); 57 | } else { 58 | res.render('profile', { username: req.user.username, verified: req.user.isVerified, err: "Invalid token or Token has expired, Try again." }); 59 | } 60 | } else { 61 | // doesnt have a token 62 | // I will simply redirect to profile 63 | res.redirect('/dashboard'); 64 | } 65 | }); 66 | 67 | router.get('/user/forgot-password', async (req, res) => { 68 | // render reset password page 69 | // not checking if user is authenticated 70 | // so that you can use as an option to change password too 71 | res.render('forgot-password.ejs', { csrfToken: req.csrfToken() }); 72 | 73 | }); 74 | 75 | router.post('/user/forgot-password', async (req, res) => { 76 | const { email } = req.body; 77 | // not checking if the field is empty or not 78 | // check if a user existss with this email 79 | var userData = await user.findOne({ email: email }); 80 | console.log(userData); 81 | if (userData) { 82 | if (userData.provider == 'google') { 83 | // type is for bootstrap alert types 84 | res.render('forgot-password.ejs', { csrfToken: req.csrfToken(), msg: "User exists with Google account. Try resetting your google account password or logging using it.", type: 'danger' }); 85 | } else { 86 | // user exists and is not with google 87 | // generate token 88 | var token = crypto.randomBytes(32).toString('hex'); 89 | // add that to database 90 | await resetToken({ token: token, email: email }).save(); 91 | // send an email for verification 92 | mailer.sendResetEmail(email, token); 93 | 94 | res.render('forgot-password.ejs', { csrfToken: req.csrfToken(), msg: "Reset email sent. Check your email for more info.", type: 'success' }); 95 | } 96 | } else { 97 | res.render('forgot-password.ejs', { csrfToken: req.csrfToken(), msg: "No user Exists with this email.", type: 'danger' }); 98 | 99 | } 100 | }); 101 | 102 | router.get('/user/reset-password', async (req, res) => { 103 | // do as in user verify , first check for a valid token 104 | // and if the token is valid send the forgot password page to show the option to change password 105 | 106 | const token = req.query.token; 107 | if (token) { 108 | var check = await resetToken.findOne({ token: token }); 109 | if (check) { 110 | // token verified 111 | // send forgot-password page with reset to true 112 | // this will render the form to reset password 113 | // sending token too to grab email later 114 | res.render('forgot-password.ejs', { csrfToken: req.csrfToken(), reset: true, email: check.email }); 115 | } else { 116 | res.render('forgot-password.ejs', { csrfToken: req.csrfToken(), msg: "Token Tampered or Expired.", type: 'danger' }); 117 | } 118 | } else { 119 | // doesnt have a token 120 | // I will simply redirect to profile 121 | res.redirect('/login'); 122 | } 123 | 124 | }); 125 | 126 | 127 | router.post('/user/reset-password', async (req, res) => { 128 | // get passwords 129 | const { password, password2, email } = req.body; 130 | console.log(password); 131 | console.log(password2); 132 | if (!password || !password2 || (password2 != password)) { 133 | res.render('forgot-password.ejs', { csrfToken: req.csrfToken(), reset: true, err: "Passwords Don't Match !", email: email }); 134 | } else { 135 | // encrypt the password 136 | var salt = await bcryptjs.genSalt(12); 137 | if (salt) { 138 | var hash = await bcryptjs.hash(password, salt); 139 | await user.findOneAndUpdate({ email: email }, { $set: { password: hash } }); 140 | res.redirect('/login'); 141 | } else { 142 | res.render('forgot-password.ejs', { csrfToken: req.csrfToken(), reset: true, err: "Unexpected Error Try Again", email: email }); 143 | 144 | } 145 | } 146 | }); 147 | 148 | 149 | module.exports = router; -------------------------------------------------------------------------------- /views/dashboard.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 14 | 15 | 16 | 17 | 20 | 21 | 22 | Dashboard | Shortly 23 | 24 | 44 | 45 | 46 | 47 | 48 | 80 | 81 |
82 | <% if(!verified){ %> 83 | 88 | <% } %> 89 | <% if(typeof emailsent !='undefined' ){ %> 90 | <% if(emailsent){ %> 91 | 95 | <% } %> 96 | <% } %> 97 |
98 |
99 | 100 | <% if(typeof err !='undefined' ){ %> 101 | <% if(err){ %> 102 | 108 | 109 | <% } %> 110 | <% } %> 111 | 112 |
113 | 114 |
115 | 116 | 118 |
119 |
120 | 121 | 123 |
124 |
125 | 127 | 128 |
129 |
130 |
131 |
132 |
133 | 134 | 135 | 136 | 137 | 138 |
139 | 140 |

Your Urls and Performance

141 |
142 | 143 | <% if(typeof urls !='undefined' ){ %> 144 | <% if(urls.length !=0){ %> 145 | 146 | <% urls.forEach((url)=> { %> 147 |
148 |
149 |
150 | 151 |
152 |
153 |
154 |

155 | Total
156 | Visits 157 |

158 |

159 | <%= url['visits'] %> 160 |

161 |
162 |
163 |
164 |
165 |
166 |
167 |

168 | Facebook
169 | Visits 170 |

171 |

172 | <%= url['visitsFB'] %> 173 |

174 |
175 |
176 |
177 |
178 |
179 |
180 |

181 | Instagram
182 | Visits 183 |

184 |

185 | <%= url['visitsIG'] %> 186 |

187 |
188 |
189 |
190 |
191 |
192 |
193 |

194 | Youtube
195 | Visits 196 |

197 |

198 | <%= url['visitsYT'] %> 199 |

200 |
201 |
202 |
203 |
204 |
205 |
206 | 207 |
208 |
209 | Visit Short Url 210 |
211 |
212 | <% }) %> 213 | 214 | <% }else{ %> 215 |

No Urls created By You 😔😔

216 | <% } %> 217 | <% } %> 218 | 219 |
220 |
221 | 222 | 223 | 224 | <%- include('./partials/footer.ejs'); %> 225 | 226 | 229 | 230 | 233 | 234 | 235 | 264 | 265 | 266 | 267 | --------------------------------------------------------------------------------