├── .gitignore ├── views ├── partials │ ├── _add_button.hbs │ └── _header.hbs ├── login.hbs ├── layouts │ ├── login.hbs │ └── main.hbs ├── stories │ ├── show.hbs │ ├── index.hbs │ ├── add.hbs │ └── edit.hbs └── dashboard.hbs ├── middlewear └── auth.js ├── routs ├── auth.js ├── index.js └── stories.js ├── config ├── db.js └── passport.js ├── public └── css │ └── style.css ├── models ├── Story.js └── User.js ├── package.json ├── Readme.md ├── helpers └── hbs.js └── app.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | config.env 3 | .env 4 | -------------------------------------------------------------------------------- /views/partials/_add_button.hbs: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 |
-------------------------------------------------------------------------------- /views/login.hbs: -------------------------------------------------------------------------------- 1 |
2 |

Life Stories

3 |
4 |

Create stories that inspire

5 |
6 | 7 |
8 |
9 | 10 | Sign in with Google 11 | 12 |
13 | 14 |
-------------------------------------------------------------------------------- /middlewear/auth.js: -------------------------------------------------------------------------------- 1 | module.exports={ 2 | ensureAuth: (req,res,next)=>{ 3 | if (req.isAuthenticated()){ 4 | return next() 5 | } 6 | else{ 7 | res.redirect('/') 8 | } 9 | }, 10 | ensureGuest: (req, res,next)=>{ 11 | if (req.isAuthenticated()){ 12 | res.redirect('/dashboard') 13 | } 14 | else{ 15 | return next() 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /routs/auth.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router= express.Router() 3 | const passport = require('passport') 4 | 5 | router.get('/google', passport.authenticate('google',{ scope : ['profile']})) 6 | 7 | router.get('/google/callback', passport.authenticate('google',{failureRedirect:'/'}),(req,res)=>{ 8 | res.redirect('/dashboard') 9 | }) 10 | 11 | router.get('/logout',(req, res)=>{ 12 | req.logOut() 13 | res.redirect('/') 14 | }) 15 | 16 | module.exports=router -------------------------------------------------------------------------------- /config/db.js: -------------------------------------------------------------------------------- 1 | const mongoose= require('mongoose') 2 | 3 | const connectDB = async () =>{ 4 | try { 5 | const conn = await mongoose.connect(process.env.MONGO_URI,{ 6 | useNewUrlParser:true, 7 | useUnifiedTopology: true, 8 | useFindAndModify: false 9 | }) 10 | 11 | console.log(`DB Connected : ${conn.connection.host}`) 12 | } catch (error) { 13 | console.error(error) 14 | process.exit(1) 15 | } 16 | } 17 | 18 | module.exports = connectDB -------------------------------------------------------------------------------- /public/css/style.css: -------------------------------------------------------------------------------- 1 | body{ 2 | background-image:url('https://source.unsplash.com/1600x900/?dark,technology'); 3 | color: #FF3031; 4 | } 5 | 6 | .login-container{ 7 | width: 400px; 8 | margin-top: 50px; 9 | text-align: center; 10 | } 11 | .fixed-action-btn{ 12 | right:50px !important; 13 | margin-left: 5%; 14 | } 15 | 16 | p{ 17 | margin: 10px 0 !important; 18 | } 19 | 20 | .btn-float{ 21 | float: left; 22 | margin-right: 10px; 23 | } 24 | .img-small{ 25 | width: 170px; 26 | } -------------------------------------------------------------------------------- /views/partials/_header.hbs: -------------------------------------------------------------------------------- 1 | 12 | -------------------------------------------------------------------------------- /models/Story.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | 3 | const storyschema = new mongoose.Schema({ 4 | title:{ 5 | type : String, 6 | required : true 7 | }, 8 | body:{ 9 | type : String, 10 | required : true 11 | }, 12 | status:{ 13 | type : String, 14 | required : 'public', 15 | enum:['public', 'private'] 16 | }, 17 | user:{ 18 | type : mongoose.Schema.Types.ObjectId, 19 | ref:'User' 20 | }, 21 | createdAt:{ 22 | type: Date, 23 | default : Date.now 24 | } 25 | }) 26 | 27 | module.exports = mongoose.model('Story',storyschema) -------------------------------------------------------------------------------- /models/User.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | 3 | const userschema = new mongoose.Schema({ 4 | googleId:{ 5 | type : String, 6 | required : true 7 | }, 8 | displayName:{ 9 | type : String, 10 | required : true 11 | }, 12 | firstName:{ 13 | type : String, 14 | required : true 15 | }, 16 | lastName:{ 17 | type : String, 18 | required : true 19 | }, 20 | image:{ 21 | type : String 22 | }, 23 | createdAt:{ 24 | type: Date, 25 | default : Date.now 26 | } 27 | }) 28 | 29 | module.exports = mongoose.model('User',userschema) -------------------------------------------------------------------------------- /routs/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router= express.Router() 3 | const { ensureAuth, ensureGuest} = require('../middlewear/auth') 4 | const story=require('../models/Story') 5 | 6 | 7 | router.get('/', ensureGuest, (req,res)=>{ 8 | res.render('login.hbs',{ 9 | layout:'login' 10 | }) 11 | }) 12 | 13 | router.get('/dashboard', ensureAuth ,async(req,res)=>{ 14 | try { 15 | const stories = await story.find({user: req.user.id}).lean() 16 | res.render('dashboard.hbs',{ 17 | name: req.user.firstName, 18 | stories 19 | }) 20 | } 21 | catch (error) { 22 | console.error(err) 23 | res.send('ERROR 500 INTERNAL SERVER ERROR') 24 | } 25 | }) 26 | 27 | module.exports=router -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "story", 3 | "version": "1.0.0", 4 | "description": "App to create a story book", 5 | "main": "app.js", 6 | "scripts": { 7 | "start": "cross-env NODE_ENV=production node app", 8 | "dev": "cross-env NODE_ENV=development nodemon app" 9 | }, 10 | "author": "Anirudh", 11 | "license": "MIT", 12 | "dependencies": { 13 | "connect-mongo": "^3.2.0", 14 | "cross-env": "^7.0.2", 15 | "dotenv": "^8.2.0", 16 | "express": "^4.17.1", 17 | "express-handlebars": "^4.0.4", 18 | "express-session": "^1.17.1", 19 | "method-override": "^3.0.0", 20 | "moment": "^2.27.0", 21 | "mongoose": "^5.9.19", 22 | "morgan": "^1.10.0", 23 | "nodemon": "^2.0.4", 24 | "passport": "^0.4.1", 25 | "passport-google-oauth20": "^2.0.0" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /views/layouts/login.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Life Stories Login 10 | 11 | 12 |
13 |
14 |
15 | {{{body}}} 16 |
17 |
18 |
19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /views/stories/show.hbs: -------------------------------------------------------------------------------- 1 |
2 |
3 |

{{stories.title}} 4 | 5 |

6 |
7 |
8 | {{formatDate date 'MMMM Do YYYY, h:mm:ss a'}} 9 | {{{stories.body}}} 10 |
11 |
12 |
13 |
14 |
15 |
16 | {{stories.user.displayName}} 17 | 18 |
19 | 22 |
23 |
24 |
-------------------------------------------------------------------------------- /views/stories/index.hbs: -------------------------------------------------------------------------------- 1 |

Stories

2 | 3 |
4 | {{#each stories}} 5 |
6 |
7 |
8 | {{{editIcon user ../user _id}}} 9 |
10 |
11 |
{{title}}
12 |

{{stripTags (truncate body) }}

13 |
14 |
15 | 16 | {{user.displayName}} 17 |
18 |
19 |
20 | Read More 21 |
22 |
23 |
24 | 25 | {{else}} 26 |

no stories to display

27 | {{/each}} 28 |
29 | 30 | 31 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | 2 | # Write Your Story 3 | 4 | This is a simpe blog made using node.js and mongoDB 5 | 6 | 7 | ## Environment Variables 8 | 9 | To run this project, you will need to add the following environment variables to your .env file 10 | 11 | `PORT = Port number 🔢 `
12 | `MONGO_URI = Your MONGODB URL 🍃 `
13 | `GOOGLE_CLIENT_ID= Google client ID for Passport 👨‍💻`
14 | `GOOGLE_CLIENT_SECRET= Your Secrete 🔐`
15 | 16 | 17 | ## Run Locally 18 | 19 | Clone the project repo 20 | 21 | ```bash 22 | git clone https://github.com/Hash-Debug/Blog 23 | ``` 24 | 25 | Go to the project directory 26 | 27 | ```bash 28 | cd Blog 29 | ``` 30 | 31 | Install dependencies 32 | 33 | ```bash 34 | npm install 35 | ``` 36 | Install nodemon globally 37 | 38 | ```bash 39 | npm install -g nodemon 40 | ``` 41 | 42 | Start the server 43 | 44 | ```bash 45 | npm run dev 46 | ``` 47 | 48 | 49 | ## Tech Stack 50 | 51 | **Client:** Handlebars, CSS 52 | 53 | **Server:** Node, Express 54 | 55 | **Database:** MongoDB 56 | 57 | **Auth:** Passport (Google OAuth) 58 | 59 | -------------------------------------------------------------------------------- /helpers/hbs.js: -------------------------------------------------------------------------------- 1 | const moment = require('moment') 2 | 3 | module.exports={ 4 | formatDate: (date,format)=>{ 5 | return moment(date).format(format) 6 | }, 7 | truncate :(str)=>{ 8 | if (str.length>150 && str.length>0){ 9 | let news=str + '' 10 | news= str.substr(0,150) 11 | news= str.substr(0,news.lastIndexOf('')) 12 | news= news.length>0? news:str.substr(0,150) 13 | return news + '....' 14 | } 15 | return str 16 | }, 17 | stripTags: (input)=>{ 18 | return input.replace(/<(?:.|\n)*?>/gm, '') 19 | }, 20 | editIcon: (storyUser, loggedUser, StoryId,floating=true)=>{ 21 | if (storyUser._id.toString() == loggedUser._id.toString()){ 22 | if (floating){ 23 | return `` 24 | } 25 | else{ 26 | return `` 27 | } 28 | } 29 | else{ 30 | return '' 31 | } 32 | }, 33 | } 34 | 35 | 36 | -------------------------------------------------------------------------------- /views/layouts/main.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Life Stories 11 | 12 | 13 | 14 | {{>_header}} 15 | {{>_add_button}} 16 |
17 | {{{body}}} 18 |
19 | 20 | 21 | 22 | 30 | 31 | -------------------------------------------------------------------------------- /views/stories/add.hbs: -------------------------------------------------------------------------------- 1 |

2 | ADD YOUR STORY 3 |

4 |
5 |
6 |
7 |
8 | 9 | 10 |
11 |
12 | 13 |
14 |
15 | 19 | 20 |
21 |
22 | 23 |
24 |
25 |
Your Story
26 | 27 | {{!-- --}} 28 |
29 |
30 | 31 |
32 | 33 | Cancell 34 |
35 | 36 |
37 |
-------------------------------------------------------------------------------- /views/dashboard.hbs: -------------------------------------------------------------------------------- 1 |

2 | {{name}}'s Dashboard 3 |

4 | {{#if stories}} 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | {{#each stories}} 16 | 17 | 18 | 19 | 20 | 31 | 32 | {{/each}} 33 | 34 |
TitleDateVisibility
{{title}}{{formatDate createdAt 'MMMM Do YYYY, h:mm:ss a'}}{{status}} 21 | 22 | 23 | 24 |
25 | 26 | 29 |
30 |
35 | {{else}} 36 |

Coreate your First story

37 | {{/if}} 38 | -------------------------------------------------------------------------------- /config/passport.js: -------------------------------------------------------------------------------- 1 | const GoogleStrategy= require('passport-google-oauth20').Strategy 2 | const mongoose = require('mongoose') 3 | const User = require('../models/User') 4 | 5 | module.exports=function(passport){ 6 | passport.use(new GoogleStrategy({ 7 | clientID: process.env.GOOGLE_CLIENT_ID, 8 | clientSecret: process.env.GOOGLE_CLIENT_SECRET, 9 | callbackURL: 'http://localhost:3000/auth/google/callback' 10 | }, 11 | async (accessToken,refreshToken,profile,done)=>{ 12 | const newUser = { 13 | googleId: profile.id, 14 | displayName : profile.displayName, 15 | firstName: profile.name.givenName, 16 | lastName: profile.name.familyName, 17 | image: profile.photos[0].value 18 | } 19 | 20 | try{ 21 | let user = await User.findOne({googleId: profile.id}) 22 | 23 | if (user){ 24 | done(null, user) 25 | } 26 | else{ 27 | user= await User.create(newUser) 28 | done(null,user) 29 | } 30 | } 31 | catch(err){ 32 | console.log(err); 33 | } 34 | 35 | })) 36 | 37 | passport.serializeUser((user, done) => { 38 | done(null, user.id); 39 | }) 40 | 41 | passport.deserializeUser((id, done) => { 42 | User.findById(id, (err, user) => done(err, user)); 43 | }) 44 | } -------------------------------------------------------------------------------- /views/stories/edit.hbs: -------------------------------------------------------------------------------- 1 |

2 | EDIT YOUR STORY 3 |

4 |
5 |
6 | 7 |
8 |
9 | 10 | 11 |
12 |
13 | 14 |
15 |
16 | 21 | 22 |
23 |
24 | 25 |
26 |
27 |
Your Story
28 | 29 | {{!-- --}} 30 |
31 |
32 | 33 |
34 | 35 | Cancell 36 |
37 | 38 |
39 |
-------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const mongoose = require('mongoose') 3 | const path = require('path') 4 | const dotenv = require('dotenv') 5 | const morgan= require('morgan') 6 | const exphbs = require('express-handlebars') 7 | const connectDB = require('./config/db') 8 | const session = require('express-session') 9 | const passport = require('passport') 10 | const methodOverride= require('method-override') 11 | const MongoStore = require('connect-mongo')(session) 12 | 13 | dotenv.config({path:'./config/config.env'}) 14 | 15 | 16 | require('./config/passport')(passport) 17 | 18 | connectDB() 19 | 20 | const app = express() 21 | 22 | app.use(express.urlencoded({extended: false})) 23 | app.use(express.json()) 24 | 25 | app.use(methodOverride(function (req, res) { 26 | if (req.body && typeof req.body === 'object' && '_method' in req.body) { 27 | // look in urlencoded POST bodies and delete it 28 | let method = req.body._method 29 | delete req.body._method 30 | return method 31 | } 32 | })) 33 | 34 | if (process.env.NODE_ENV==='developmant'){ 35 | app.use(morgan('dev')) 36 | } 37 | 38 | 39 | const{formatDate ,stripTags, truncate, editIcon} = require ('./helpers/hbs') 40 | 41 | 42 | app.engine('.hbs', exphbs({helpers :{editIcon,formatDate,stripTags,truncate},defaultLayout:'main', extname:'.hbs'})); 43 | app.set('view enging', '.hbs'); 44 | 45 | app.use(session({ 46 | secret: "Vanakkam da mapla", 47 | resave: false, 48 | saveUninitialized: false, 49 | store: new MongoStore({ mongooseConnection: mongoose.connection}) 50 | })); 51 | 52 | app.use(passport.initialize()); 53 | app.use(passport.session()); 54 | 55 | app.use(function(req,res,next){ 56 | res.locals.user=req.user || null 57 | next() 58 | }) 59 | 60 | app.use(express.static(path.join(__dirname,'public'))) 61 | 62 | app.use('/',require('./routs/index')) 63 | app.use('/auth',require('./routs/auth')) 64 | app.use('/stories',require('./routs/stories')) 65 | 66 | const port = process.env.PORT 67 | 68 | app.listen(port,console.log(`Running server in ${process.env.NODE_ENV} mode on port ${port}`)) 69 | -------------------------------------------------------------------------------- /routs/stories.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router= express.Router() 3 | const { ensureAuth} = require('../middlewear/auth') 4 | const story=require('../models/Story') 5 | 6 | 7 | router.get('/add', ensureAuth, (req,res)=>{ 8 | res.render('stories/add.hbs') 9 | }) 10 | 11 | router.post('/', ensureAuth, async(req,res)=>{ 12 | try { 13 | req.body.user=req.user.id 14 | await story.create(req.body) 15 | res.redirect('/dashboard') 16 | } catch (error) { 17 | console.error(error) 18 | res.send('ERROR 500 UNABLE TO CONNECT TO SERVER') 19 | } 20 | }) 21 | 22 | router.get('/', ensureAuth, async (req,res)=>{ 23 | try { 24 | const stories = await story.find({status: 'public'}) 25 | .populate('user') 26 | .sort({createdAt:'desc'}) 27 | .lean() 28 | 29 | res.render('stories/index.hbs',{stories}) 30 | } catch (error) { 31 | console.error(error) 32 | res.send('ERROR 500 UNABLE TO CONNECT TO SERVER') 33 | } 34 | }) 35 | 36 | 37 | router.get('/:id', ensureAuth, async(req,res)=>{ 38 | try { 39 | let stories= await story.findById(req.params.id) 40 | .populate('user') 41 | .lean() 42 | 43 | if (!stories){ 44 | return res.send("404 ERROR NOT FOUND") 45 | } 46 | res.render('stories/show.hbs',{stories}) 47 | 48 | 49 | } catch (error) { 50 | console.error(error); 51 | return res.send("404 PAGE NOT FOUND") 52 | } 53 | 54 | }) 55 | 56 | 57 | 58 | router.get('/edit/:id',ensureAuth, async(req, res) => { 59 | try { 60 | const stories = await story.findOne({ 61 | _id: req.params.id 62 | }).lean() 63 | 64 | if (! stories){ 65 | return res.send('ERROR 404 NOT FOUND') 66 | } 67 | if (stories.user != req.user.id){ 68 | res.redirect('/stories') 69 | } 70 | else{ 71 | res.render('stories/edit.hbs',{ 72 | stories, 73 | }) 74 | } 75 | } catch (error) { 76 | console.error(error); 77 | return res.send("ERROR 500 SERVER REFUSED TO CONNECT") 78 | } 79 | }) 80 | 81 | router.put('/:id', ensureAuth,async (req,res)=>{ 82 | try { 83 | let stories = await story.findById(req.params.id).lean() 84 | 85 | if (!stories){ 86 | return res.send("ERROR 404 NOT FOUND") 87 | } 88 | if (stories.user != req.user.id){ 89 | res.redirect('/stories') 90 | } 91 | else{ 92 | stories= await story.findByIdAndUpdate({_id: req.params.id},req.body,{ 93 | new:false, 94 | runValidators : true 95 | }) 96 | res.redirect('/dashboard') 97 | } 98 | } catch (error) { 99 | console.error(error); 100 | return res.send("ERROR 500 SERVER COULDNT CONNECT") 101 | } 102 | }) 103 | 104 | 105 | router.delete('/:id', ensureAuth,async (req,res)=>{ 106 | try { 107 | await story.remove({_id:req.params.id}) 108 | res.redirect('/dashboard') 109 | } catch (error) { 110 | console.error(error) 111 | return res.send("ERROR 500 COULDNT CONNECT TO SERVER") 112 | } 113 | 114 | }) 115 | 116 | 117 | router.get('/user/:userId', ensureAuth, async(req,res)=>{ 118 | try { 119 | const stories= await story.find({ 120 | user: req.params.userId, 121 | status:'public' 122 | }) 123 | .populate('user') 124 | .lean() 125 | return res.render('stories/index.hbs',{stories}) 126 | } catch (error) { 127 | console.error(error); 128 | return res.send("ERROR 500 SERVER REFUSED TO CONNECT") 129 | } 130 | }) 131 | 132 | 133 | module.exports=router --------------------------------------------------------------------------------