diff --git a/package-lock.json b/package-lock.json index 909a598..caf05c4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,8 @@ "express-session": "^1.17.3", "jsonwebtoken": "^9.0.0", "method-override": "^3.0.0", - "mongoose": "^7.0.3" + "mongoose": "^7.0.3", + "slugify": "^1.6.6" }, "devDependencies": { "nodemon": "^2.0.22" @@ -1863,6 +1864,14 @@ "semver": "bin/semver.js" } }, + "node_modules/slugify": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.6.tgz", + "integrity": "sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/smart-buffer": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", @@ -3507,6 +3516,11 @@ } } }, + "slugify": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.6.tgz", + "integrity": "sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==" + }, "smart-buffer": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", diff --git a/package.json b/package.json index 22dc07f..be44775 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,8 @@ "express-session": "^1.17.3", "jsonwebtoken": "^9.0.0", "method-override": "^3.0.0", - "mongoose": "^7.0.3" + "mongoose": "^7.0.3", + "slugify": "^1.6.6" }, "devDependencies": { "nodemon": "^2.0.22" diff --git a/server/models/Post.js b/server/models/Post.js index c0627e1..2789c3a 100644 --- a/server/models/Post.js +++ b/server/models/Post.js @@ -1,23 +1,34 @@ -const mongoose = require('mongoose'); - +const mongoose = require("mongoose"); +const slugify = require("slugify"); const Schema = mongoose.Schema; const PostSchema = new Schema({ title: { type: String, - required: true + required: true, }, body: { type: String, - required: true + required: true, + }, + slug: { + type: String, + required: true, + unique: true, }, createdAt: { type: Date, - default: Date.now + default: Date.now, }, updatedAt: { type: Date, - default: Date.now - } + default: Date.now, + }, }); +PostSchema.pre("validate", function (next) { + if (this.title) { + this.slug = slugify(this.title, { lower: true, strict: true }); + } -module.exports = mongoose.model('Post', PostSchema); \ No newline at end of file + next(); +}); +module.exports = mongoose.model("Post", PostSchema); diff --git a/server/routes/admin.js b/server/routes/admin.js index a843458..5117575 100644 --- a/server/routes/admin.js +++ b/server/routes/admin.js @@ -1,209 +1,190 @@ -const express = require('express'); +const express = require("express"); const router = express.Router(); -const Post = require('../models/Post'); -const User = require('../models/User'); -const bcrypt = require('bcrypt'); -const jwt = require('jsonwebtoken'); +const Post = require("../models/Post"); +const User = require("../models/User"); +const bcrypt = require("bcrypt"); +const jwt = require("jsonwebtoken"); -const adminLayout = '../views/layouts/admin'; +const adminLayout = "../views/layouts/admin"; const jwtSecret = process.env.JWT_SECRET; - /** - * + * * Check Login -*/ -const authMiddleware = (req, res, next ) => { + */ +const authMiddleware = (req, res, next) => { const token = req.cookies.token; - if(!token) { - return res.status(401).json( { message: 'Unauthorized'} ); + if (!token) { + return res.status(401).json({ message: "Unauthorized" }); } try { const decoded = jwt.verify(token, jwtSecret); req.userId = decoded.userId; next(); - } catch(error) { - res.status(401).json( { message: 'Unauthorized'} ); + } catch (error) { + res.status(401).json({ message: "Unauthorized" }); } -} - +}; /** * GET / * Admin - Login Page -*/ -router.get('/admin', async (req, res) => { + */ +router.get("/admin", async (req, res) => { try { const locals = { title: "Admin", - description: "Simple Blog created with NodeJs, Express & MongoDb." - } + description: "Simple Blog created with NodeJs, Express & MongoDb.", + }; - res.render('admin/index', { locals, layout: adminLayout }); + res.render("admin/index", { locals, layout: adminLayout }); } catch (error) { console.log(error); } }); - /** * POST / * Admin - Check Login -*/ -router.post('/admin', async (req, res) => { + */ +router.post("/admin", async (req, res) => { try { const { username, password } = req.body; - - const user = await User.findOne( { username } ); - if(!user) { - return res.status(401).json( { message: 'Invalid credentials' } ); + const user = await User.findOne({ username }); + + if (!user) { + return res.status(401).json({ message: "Invalid credentials" }); } const isPasswordValid = await bcrypt.compare(password, user.password); - if(!isPasswordValid) { - return res.status(401).json( { message: 'Invalid credentials' } ); + if (!isPasswordValid) { + return res.status(401).json({ message: "Invalid credentials" }); } - const token = jwt.sign({ userId: user._id}, jwtSecret ); - res.cookie('token', token, { httpOnly: true }); - res.redirect('/dashboard'); - + const token = jwt.sign({ userId: user._id }, jwtSecret); + res.cookie("token", token, { httpOnly: true }); + res.redirect("/dashboard"); } catch (error) { console.log(error); } }); - /** * GET / * Admin Dashboard -*/ -router.get('/dashboard', authMiddleware, async (req, res) => { + */ +router.get("/dashboard", authMiddleware, async (req, res) => { try { const locals = { - title: 'Dashboard', - description: 'Simple Blog created with NodeJs, Express & MongoDb.' - } + title: "Dashboard", + description: "Simple Blog created with NodeJs, Express & MongoDb.", + }; const data = await Post.find(); - res.render('admin/dashboard', { + res.render("admin/dashboard", { locals, data, - layout: adminLayout + layout: adminLayout, }); - } catch (error) { console.log(error); } - }); - /** * GET / * Admin - Create New Post -*/ -router.get('/add-post', authMiddleware, async (req, res) => { + */ +router.get("/add-post", authMiddleware, async (req, res) => { try { const locals = { - title: 'Add Post', - description: 'Simple Blog created with NodeJs, Express & MongoDb.' - } + title: "Add Post", + description: "Simple Blog created with NodeJs, Express & MongoDb.", + }; const data = await Post.find(); - res.render('admin/add-post', { + res.render("admin/add-post", { locals, - layout: adminLayout + layout: adminLayout, }); - } catch (error) { console.log(error); } - }); - /** * POST / * Admin - Create New Post -*/ -router.post('/add-post', authMiddleware, async (req, res) => { + */ +router.post("/add-post", authMiddleware, async (req, res) => { try { try { const newPost = new Post({ title: req.body.title, - body: req.body.body + body: req.body.body, }); await Post.create(newPost); - res.redirect('/dashboard'); + res.redirect("/dashboard"); } catch (error) { console.log(error); } - } catch (error) { console.log(error); } }); - /** * GET / * Admin - Create New Post -*/ -router.get('/edit-post/:id', authMiddleware, async (req, res) => { + */ +router.get("/edit-post/:id", authMiddleware, async (req, res) => { try { - + let slug = req.params.id; const locals = { title: "Edit Post", description: "Free NodeJs User Management System", }; - const data = await Post.findOne({ _id: req.params.id }); + const data = await Post.findOne({ slug }); - res.render('admin/edit-post', { + res.render("admin/edit-post", { locals, data, - layout: adminLayout - }) - + layout: adminLayout, + }); } catch (error) { console.log(error); } - }); - /** * PUT / * Admin - Create New Post -*/ -router.put('/edit-post/:id', authMiddleware, async (req, res) => { + */ +router.put("/edit-post/:id", authMiddleware, async (req, res) => { try { - - await Post.findByIdAndUpdate(req.params.id, { + let slug = req.params.id; + await Post.findOneAndUpdate(slug, { title: req.body.title, body: req.body.body, - updatedAt: Date.now() + updatedAt: Date.now(), }); - res.redirect(`/edit-post/${req.params.id}`); - + res.redirect(`/edit-post/${slug}`); } catch (error) { console.log(error); } - }); - // router.post('/admin', async (req, res) => { // try { // const { username, password } = req.body; - + // if(req.body.username === 'admin' && req.body.password === 'password') { // res.send('You are logged in.') // } else { @@ -215,57 +196,51 @@ router.put('/edit-post/:id', authMiddleware, async (req, res) => { // } // }); - /** * POST / * Admin - Register -*/ -router.post('/register', async (req, res) => { + */ +router.post("/register", async (req, res) => { try { const { username, password } = req.body; const hashedPassword = await bcrypt.hash(password, 10); try { - const user = await User.create({ username, password:hashedPassword }); - res.status(201).json({ message: 'User Created', user }); + const user = await User.create({ username, password: hashedPassword }); + res.status(201).json({ message: "User Created", user }); } catch (error) { - if(error.code === 11000) { - res.status(409).json({ message: 'User already in use'}); + if (error.code === 11000) { + res.status(409).json({ message: "User already in use" }); } - res.status(500).json({ message: 'Internal server error'}) + res.status(500).json({ message: "Internal server error" }); } - } catch (error) { console.log(error); } }); - /** * DELETE / * Admin - Delete Post -*/ -router.delete('/delete-post/:id', authMiddleware, async (req, res) => { - + */ +router.delete("/delete-post/:id", authMiddleware, async (req, res) => { try { - await Post.deleteOne( { _id: req.params.id } ); - res.redirect('/dashboard'); + let slug = req.params.id; + await Post.deleteOne({ slug }); + res.redirect("/dashboard"); } catch (error) { console.log(error); } - }); - /** * GET / * Admin Logout -*/ -router.get('/logout', (req, res) => { - res.clearCookie('token'); + */ +router.get("/logout", (req, res) => { + res.clearCookie("token"); //res.json({ message: 'Logout successful.'}); - res.redirect('/'); + res.redirect("/"); }); - -module.exports = router; \ No newline at end of file +module.exports = router; diff --git a/server/routes/main.js b/server/routes/main.js index a97ab9e..e5a54ba 100644 --- a/server/routes/main.js +++ b/server/routes/main.js @@ -1,25 +1,25 @@ -const express = require('express'); +const express = require("express"); const router = express.Router(); -const Post = require('../models/Post'); +const Post = require("../models/Post"); /** * GET / * HOME -*/ -router.get('', async (req, res) => { + */ +router.get("", async (req, res) => { try { const locals = { title: "NodeJs Blog", - description: "Simple Blog created with NodeJs, Express & MongoDb." - } + description: "Simple Blog created with NodeJs, Express & MongoDb.", + }; let perPage = 10; let page = req.query.page || 1; - const data = await Post.aggregate([ { $sort: { createdAt: -1 } } ]) - .skip(perPage * page - perPage) - .limit(perPage) - .exec(); + const data = await Post.aggregate([{ $sort: { createdAt: -1 } }]) + .skip(perPage * page - perPage) + .limit(perPage) + .exec(); // Count is deprecated - please use countDocuments // const count = await Post.count(); @@ -27,18 +27,16 @@ router.get('', async (req, res) => { const nextPage = parseInt(page) + 1; const hasNextPage = nextPage <= Math.ceil(count / perPage); - res.render('index', { + res.render("index", { locals, data, current: page, nextPage: hasNextPage ? nextPage : null, - currentRoute: '/' + currentRoute: "/", }); - } catch (error) { console.log(error); } - }); // router.get('', async (req, res) => { @@ -56,79 +54,69 @@ router.get('', async (req, res) => { // }); - /** * GET / * Post :id -*/ -router.get('/post/:id', async (req, res) => { + */ +router.get("/post/:id", async (req, res) => { try { let slug = req.params.id; - - const data = await Post.findById({ _id: slug }); - + const data = await Post.findOne({ slug }); const locals = { title: data.title, description: "Simple Blog created with NodeJs, Express & MongoDb.", - } - - res.render('post', { + }; + res.render("post", { locals, data, - currentRoute: `/post/${slug}` + currentRoute: `/post/${slug}`, }); } catch (error) { console.log(error); } - }); - /** * POST / * Post - searchTerm -*/ -router.post('/search', async (req, res) => { + */ +router.post("/search", async (req, res) => { try { const locals = { title: "Seach", - description: "Simple Blog created with NodeJs, Express & MongoDb." - } + description: "Simple Blog created with NodeJs, Express & MongoDb.", + }; let searchTerm = req.body.searchTerm; - const searchNoSpecialChar = searchTerm.replace(/[^a-zA-Z0-9 ]/g, "") + const searchNoSpecialChar = searchTerm.replace(/[^a-zA-Z0-9 ]/g, ""); const data = await Post.find({ $or: [ - { title: { $regex: new RegExp(searchNoSpecialChar, 'i') }}, - { body: { $regex: new RegExp(searchNoSpecialChar, 'i') }} - ] + { title: { $regex: new RegExp(searchNoSpecialChar, "i") } }, + { body: { $regex: new RegExp(searchNoSpecialChar, "i") } }, + ], }); res.render("search", { data, locals, - currentRoute: '/' + currentRoute: "/", }); - } catch (error) { console.log(error); } - }); - /** * GET / * About -*/ -router.get('/about', (req, res) => { - res.render('about', { - currentRoute: '/about' + */ +router.get("/about", (req, res) => { + res.render("about", { + currentRoute: "/about", }); }); - // function insertPostData () { // Post.insertMany([ // { @@ -176,5 +164,4 @@ router.get('/about', (req, res) => { // insertPostData(); - module.exports = router; diff --git a/views/admin/add-post.ejs b/views/admin/add-post.ejs index d993060..2a76ede 100644 --- a/views/admin/add-post.ejs +++ b/views/admin/add-post.ejs @@ -6,10 +6,10 @@