├── client
├── components
│ ├── blog
│ │ ├── Card
│ │ │ ├── index.js
│ │ │ └── Card.js
│ │ ├── Search
│ │ │ ├── index.js
│ │ │ └── Search.js
│ │ └── SmallCard
│ │ │ ├── index.js
│ │ │ └── SmallCard.js
│ ├── Layout.js
│ ├── authentication
│ │ ├── PrivateRoute.js
│ │ ├── Admin.js
│ │ ├── Register.js
│ │ └── LoginAuth.js
│ ├── LandingPage
│ │ ├── Layout.js
│ │ ├── Navbar.js
│ │ └── Footer.js
│ ├── update
│ │ ├── ReadNewBlog.js
│ │ ├── Category.js
│ │ ├── Tag.js
│ │ ├── NewBlog.js
│ │ └── UpdateNewBlog.js
│ └── Header.js
├── static
│ ├── assets
│ │ ├── 27263.jpg
│ │ ├── favicon.ico
│ │ ├── Blog-Post.png
│ │ ├── blackback.jpg
│ │ ├── svg
│ │ │ ├── Octocat.png
│ │ │ ├── github.svg
│ │ │ ├── tweet.svg
│ │ │ └── developer.svg
│ │ ├── blogNavbarIcon.png
│ │ ├── coding.svg
│ │ ├── Lantern.svg
│ │ └── freelancing.svg
│ └── img
│ │ ├── blogscroll.gif
│ │ ├── code.svg
│ │ └── terminal.svg
├── hooks
│ └── useLoaded.js
├── pages
│ ├── userDashboard
│ │ └── index.js
│ ├── login.js
│ ├── signup.js
│ ├── adminDashboard
│ │ ├── update
│ │ │ ├── [slug].js
│ │ │ ├── blog.js
│ │ │ ├── category-tag.js
│ │ │ └── editBlog.js
│ │ └── index.js
│ ├── profile
│ │ └── [username].js
│ ├── _document.js
│ ├── taglists
│ │ └── [slug].js
│ ├── categories
│ │ └── [slug].js
│ ├── blogs
│ │ ├── index.js
│ │ └── [slug].js
│ └── index.js
├── config.js
├── actions
│ ├── user.js
│ ├── tag.js
│ ├── category.js
│ ├── authentication.js
│ └── blog.js
├── helpers
│ └── ReactQuill.js
├── package.json
└── .gitignore
└── server
├── validators
├── tag.js
├── category.js
├── index.js
└── auth.js
├── helpers
├── excerptTrim.js
└── databaseErrorHandler.js
├── routes
├── user.js
├── auth.js
├── tag.js
├── category.js
└── blog.js
├── models
├── tagSchema.js
├── categorySchema.js
├── blogSchema.js
└── user.js
├── package.json
├── controllers
├── user.js
├── tag.js
├── category.js
├── userAuthentication.js
└── blog.js
├── server.js
└── .gitignore
/client/components/blog/Card/index.js:
--------------------------------------------------------------------------------
1 | import Card from "./Card";
2 |
3 | export default Card;
--------------------------------------------------------------------------------
/client/components/blog/Search/index.js:
--------------------------------------------------------------------------------
1 | import Search from "./Search";
2 |
3 | export default Search;
--------------------------------------------------------------------------------
/client/components/blog/SmallCard/index.js:
--------------------------------------------------------------------------------
1 | import SmallCard from "./SmallCard";
2 |
3 | export default SmallCard;
--------------------------------------------------------------------------------
/client/static/assets/27263.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pramit-marattha/MongoDB-React-Express-Node-Fullstack-TechBlogsite/HEAD/client/static/assets/27263.jpg
--------------------------------------------------------------------------------
/client/static/assets/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pramit-marattha/MongoDB-React-Express-Node-Fullstack-TechBlogsite/HEAD/client/static/assets/favicon.ico
--------------------------------------------------------------------------------
/client/static/img/blogscroll.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pramit-marattha/MongoDB-React-Express-Node-Fullstack-TechBlogsite/HEAD/client/static/img/blogscroll.gif
--------------------------------------------------------------------------------
/client/static/assets/Blog-Post.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pramit-marattha/MongoDB-React-Express-Node-Fullstack-TechBlogsite/HEAD/client/static/assets/Blog-Post.png
--------------------------------------------------------------------------------
/client/static/assets/blackback.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pramit-marattha/MongoDB-React-Express-Node-Fullstack-TechBlogsite/HEAD/client/static/assets/blackback.jpg
--------------------------------------------------------------------------------
/client/static/assets/svg/Octocat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pramit-marattha/MongoDB-React-Express-Node-Fullstack-TechBlogsite/HEAD/client/static/assets/svg/Octocat.png
--------------------------------------------------------------------------------
/client/static/assets/blogNavbarIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pramit-marattha/MongoDB-React-Express-Node-Fullstack-TechBlogsite/HEAD/client/static/assets/blogNavbarIcon.png
--------------------------------------------------------------------------------
/server/validators/tag.js:
--------------------------------------------------------------------------------
1 | const {check} = require("express-validator");
2 |
3 | exports.createTagValidator = [
4 | check('name').not().isEmpty().withMessage("hold up!! Name is required")
5 | ];
--------------------------------------------------------------------------------
/server/validators/category.js:
--------------------------------------------------------------------------------
1 | const {check} = require("express-validator");
2 |
3 | exports.createCategoryValidator = [
4 | check('name').not().isEmpty().withMessage("hold up!! Name is required")
5 | ];
--------------------------------------------------------------------------------
/client/hooks/useLoaded.js:
--------------------------------------------------------------------------------
1 | import React,{useState,useEffect} from 'react'
2 |
3 | export const useLoaded = () => {
4 | const [loaded, setLoaded] = useState(false);
5 | useEffect(() => setLoaded(true), []);
6 | return loaded;
7 | };
8 |
9 |
--------------------------------------------------------------------------------
/client/static/img/code.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/client/static/img/terminal.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/server/validators/index.js:
--------------------------------------------------------------------------------
1 | const {validationResult} = require("express-validator");
2 |
3 | // middlewares
4 | exports.runValidation = (req,res,next) =>{
5 | const errors = validationResult(req);
6 | if (!errors.isEmpty()){
7 | return res.status(422).json({error:errors.array()[0].msg})
8 | }
9 | next();
10 | };
11 |
--------------------------------------------------------------------------------
/client/components/Layout.js:
--------------------------------------------------------------------------------
1 | import React,{useState,useEffect} from "react";
2 | import Header from "./Header";
3 |
4 |
5 | const Layout = ({children}) =>{
6 |
7 | return (
8 | <>
9 |
10 | {children}
11 | {/*
footer
*/}
12 | >
13 | )
14 | };
15 |
16 | export default Layout;
--------------------------------------------------------------------------------
/server/helpers/excerptTrim.js:
--------------------------------------------------------------------------------
1 | exports.excerptTrim = (str, length, delim, appendix) => {
2 | if (str.length <= length) return str;
3 | var trimmedString = str.substr(0, length + delim.length);
4 | var lastDelimIndex = trimmedString.lastIndexOf(delim);
5 | if (lastDelimIndex >= 0) trimmedString = trimmedString.substr(0, lastDelimIndex);
6 | if (trimmedString) trimmedString += appendix;
7 | return trimmedString;
8 | };
--------------------------------------------------------------------------------
/client/pages/userDashboard/index.js:
--------------------------------------------------------------------------------
1 | import Layout from "../../components/Layout";
2 | import PrivateRoute from "../../components/authentication/PrivateRoute";
3 | import Link from "next/link";
4 |
5 |
6 | const UserIndex =() =>{
7 | return(
8 |
9 |
10 | User Dashboard
11 |
12 |
13 | )
14 | }
15 |
16 | export default UserIndex;
--------------------------------------------------------------------------------
/client/config.js:
--------------------------------------------------------------------------------
1 | import getConfig from "next/config";
2 |
3 | const {publicRuntimeConfig} = getConfig();
4 | export const API = publicRuntimeConfig.PRODUCTION ? publicRuntimeConfig.PRODUCTION_SITE : publicRuntimeConfig.DEVELOPMENT_SITE ;
5 | export const APP_NAME = publicRuntimeConfig.APP_NAME;
6 |
7 |
8 | export const DOMAIN = publicRuntimeConfig.PRODUCTION ? publicRuntimeConfig.DOMAIN_PRODUCTION : publicRuntimeConfig.DOMAIN_DEVELOPMENT ;
9 |
--------------------------------------------------------------------------------
/server/routes/user.js:
--------------------------------------------------------------------------------
1 | const express = require('express')
2 | const router = express.Router();
3 | const {requireLogin,authenticationMiddleware} = require("../controllers/userAuthentication.js")
4 | const {profileReadRequest,publicProfileReadRequest} = require("../controllers/user.js")
5 |
6 | router.get('/profile',requireLogin,authenticationMiddleware,profileReadRequest);
7 | router.get('/user/:username',publicProfileReadRequest);
8 |
9 | module.exports = router;
10 |
--------------------------------------------------------------------------------
/client/actions/user.js:
--------------------------------------------------------------------------------
1 | import fetch from "isomorphic-fetch";
2 | import { API } from "../config.js";
3 |
4 | export const userPublicProfile = (username) => {
5 | return fetch(`${API}/api/user/${username}`, {
6 | method: 'GET',
7 | headers: {
8 | Accept: 'application/json',
9 |
10 | }
11 | })
12 | .then(response => {
13 | return response.json();
14 | })
15 | .catch(error => console.log(error));
16 | };
--------------------------------------------------------------------------------
/server/models/tagSchema.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 |
3 | const TagSchema = new mongoose.Schema(
4 | {
5 | name: {
6 | type: String,
7 | trim: true,
8 | required: true,
9 | max: 32
10 | },
11 | slug: {
12 | type: String,
13 | unique: true,
14 | index: true
15 | }
16 | },
17 | { timestamps: true }
18 | );
19 |
20 | module.exports = mongoose.model("Tag", TagSchema);
--------------------------------------------------------------------------------
/client/components/authentication/PrivateRoute.js:
--------------------------------------------------------------------------------
1 | import React ,{useState,useEffect} from "react";
2 | import Router from "next/router";
3 | import {isAuthenticated} from "../../actions/authentication";
4 |
5 | const PrivateRoute = ({children})=>{
6 |
7 |
8 | useEffect(()=>{
9 | if(!isAuthenticated()){
10 | Router.push(`/login`)
11 | }
12 | },[])
13 |
14 | return (
15 | <>
16 | {children}
17 | >
18 | )
19 |
20 | }
21 |
22 | export default PrivateRoute;
--------------------------------------------------------------------------------
/server/models/categorySchema.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 |
3 | const CategorySchema = new mongoose.Schema(
4 | {
5 | name: {
6 | type: String,
7 | trim: true,
8 | required: true,
9 | max: 32
10 | },
11 | slug: {
12 | type: String,
13 | unique: true,
14 | index: true
15 | }
16 | },
17 | { timestamps: true }
18 | );
19 |
20 | module.exports = mongoose.model("Category", CategorySchema);
--------------------------------------------------------------------------------
/client/pages/login.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Layout from "../components/Layout";
3 | import LoginAuth from "../components/authentication/LoginAuth";
4 | import Link from "next/link";
5 |
6 | const Login =() =>{
7 | return(
8 |
9 | Login
10 |
15 |
16 | )
17 | }
18 |
19 | export default Login;
--------------------------------------------------------------------------------
/client/pages/signup.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Layout from "../components/Layout";
3 | import Register from "../components/authentication/Register";
4 | import Link from "next/link";
5 |
6 | const Signup =() =>{
7 | return(
8 |
9 | Register New Account
10 |
15 |
16 | )
17 | }
18 |
19 | export default Signup;
--------------------------------------------------------------------------------
/client/components/authentication/Admin.js:
--------------------------------------------------------------------------------
1 | import React ,{useState,useEffect} from "react";
2 | import Router from "next/router";
3 | import {isAuthenticated} from "../../actions/authentication";
4 |
5 | const Admin = ({children})=>{
6 |
7 |
8 | useEffect(()=>{
9 | if(!isAuthenticated()){
10 | Router.push(`/login`);
11 | }else if (isAuthenticated().role !== 1){
12 | Router.push(`/`);
13 | }
14 | },[])
15 |
16 | return (
17 | <>
18 | {children}
19 | >
20 | )
21 |
22 | }
23 |
24 | export default Admin;
--------------------------------------------------------------------------------
/server/routes/auth.js:
--------------------------------------------------------------------------------
1 | const express = require('express')
2 | const router = express.Router();
3 | const {signup,login,logout} = require("../controllers/userAuthentication.js")
4 | // const {signin} = require("../controllers")
5 | // importing validators
6 | const {runValidation} = require("../validators")
7 | const {userSignupValidator,userLoginValidator} = require("../validators/auth")
8 |
9 | router.post('/signup',userSignupValidator,runValidation,signup);
10 | router.post('/login',userLoginValidator,runValidation,login);
11 | router.get('/logout',logout);
12 |
13 | module.exports = router;
14 |
--------------------------------------------------------------------------------
/server/validators/auth.js:
--------------------------------------------------------------------------------
1 | const {check} = require("express-validator");
2 |
3 | exports.userSignupValidator = [
4 | check('name').not().isEmpty().withMessage("hold up!! Name is required"),
5 | check('email').isEmail().withMessage("Please enter valid email address"),
6 | check('password').isLength({min:6}).withMessage("password must be 6 characters"),
7 | ];
8 |
9 | exports.userLoginValidator = [
10 | check('email').isEmail().withMessage("Please enter valid email address"),
11 | check('password').isLength({min:6}).withMessage("password must be 6 characters"),
12 | ];
13 |
14 |
--------------------------------------------------------------------------------
/server/routes/tag.js:
--------------------------------------------------------------------------------
1 | const express = require('express')
2 | const router = express.Router();
3 | const {create,list,read,remove} = require("../controllers/tag.js")
4 | const {requireLogin,adminAuthenticationMiddleware} = require("../controllers/userAuthentication.js")
5 |
6 | const {runValidation} = require("../validators")
7 | const {createTagValidator} = require("../validators/Tag")
8 |
9 | router.post('/tag',createTagValidator,runValidation,requireLogin,adminAuthenticationMiddleware,create);
10 | router.get('/taglists',list)
11 | router.get('/tag/:slug',read)
12 | router.delete('/tag/:slug',requireLogin,adminAuthenticationMiddleware,remove)
13 |
14 | module.exports = router;
15 |
--------------------------------------------------------------------------------
/client/components/LandingPage/Layout.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Navbar from './Navbar'
3 | import Footer from './Footer'
4 | import Head from 'next/head'
5 |
6 | const Layout = (props) => {
7 | return (
8 |
9 |
10 |
14 |
15 | Tech BlogSite{props.title}
16 |
17 |
18 |
19 | {props.children}
20 |
21 |
22 | )
23 | }
24 |
25 | export default Layout
26 |
--------------------------------------------------------------------------------
/server/routes/category.js:
--------------------------------------------------------------------------------
1 | const express = require('express')
2 | const router = express.Router();
3 | const {create,list,read,remove} = require("../controllers/category.js")
4 | const {requireLogin,adminAuthenticationMiddleware} = require("../controllers/userAuthentication.js")
5 |
6 | const {runValidation} = require("../validators")
7 | const {createCategoryValidator} = require("../validators/category")
8 |
9 | router.post('/category',createCategoryValidator,runValidation,requireLogin,adminAuthenticationMiddleware,create);
10 | router.get('/categories',list)
11 | router.get('/category/:slug',read)
12 | router.delete('/category/:slug',requireLogin,adminAuthenticationMiddleware,remove)
13 |
14 | module.exports = router;
15 |
--------------------------------------------------------------------------------
/client/static/assets/svg/github.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/client/helpers/ReactQuill.js:
--------------------------------------------------------------------------------
1 | export const ReactQuillModules = {
2 | toolbar: [
3 | [{ header: '1' }, { header: '2' }, { header: [3, 4, 5, 6] }, { font: [] }],
4 | [{ size: [] }],
5 | ['bold', 'italic', 'underline', 'strike', 'blockquote'],
6 | [{ list: 'ordered' }, { list: 'bullet' },{ 'align': [] }],
7 | ['link', 'image', 'video'],
8 | [{ 'color': [] }, { 'background': [] }],
9 | ['clean'],
10 | ['code-block']
11 | ]
12 | };
13 |
14 |
15 | export const ReactQuillFormats = [
16 | "header",
17 | "font",
18 | "size",
19 | "bold",
20 | "italic",
21 | "underline",
22 | "align",
23 | "strike",
24 | "script",
25 | "blockquote",
26 | "background",
27 | "list",
28 | "bullet",
29 | "indent",
30 | "link",
31 | "image",
32 | "color",
33 | "code-block"
34 | ];
35 |
36 |
--------------------------------------------------------------------------------
/server/routes/blog.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const router = express.Router();
3 | const {create ,list,bloglistsallCategoriesTags,read,remove,update,photo,blogListRelated,listSearchItems} = require("../controllers/blog.js");
4 | const {requireLogin,adminAuthenticationMiddleware} = require("../controllers/userAuthentication.js");
5 |
6 | router.post('/blog',requireLogin,adminAuthenticationMiddleware,create);
7 | router.get('/bloglists',list);
8 | router.post('/bloglists-categories-taglists',bloglistsallCategoriesTags);
9 | router.get('/blog/:slug',read);
10 | router.delete('/blog/:slug',requireLogin,adminAuthenticationMiddleware,remove);
11 | router.put('/blog/:slug',requireLogin,adminAuthenticationMiddleware,update);
12 | router.get('/blog/photo/:slug',photo);
13 | router.post('/bloglists/related',blogListRelated);
14 | router.get('/bloglists/search',listSearchItems);
15 |
16 |
17 | module.exports = router;
18 |
--------------------------------------------------------------------------------
/server/helpers/databaseErrorHandler.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // Get unique error field name
4 |
5 | const uniqueMessage = error => {
6 | let output;
7 | try {
8 | let fieldName = error.message.substring(error.message.lastIndexOf('.$') + 2, error.message.lastIndexOf('_1'));
9 | output = fieldName.charAt(0).toUpperCase() + fieldName.slice(1) + ' already exists';
10 | } catch (ex) {
11 | output = 'Unique field already exists';
12 | }
13 |
14 | return output;
15 | };
16 |
17 |
18 | // Get the error message from error object
19 |
20 | exports.errorHandler = error => {
21 | let message = '';
22 |
23 | if (error.code) {
24 | switch (error.code) {
25 | case 11000:
26 | case 11001:
27 | message = uniqueMessage(error);
28 | break;
29 | default:
30 | message = 'Something went wrong';
31 | }
32 | } else {
33 | for (let errorName in error.errorors) {
34 | if (error.errorors[errorName].message) message = error.errorors[errorName].message;
35 | }
36 | }
37 |
38 | return message;
39 | };
--------------------------------------------------------------------------------
/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "server",
3 | "version": "1.0.0",
4 | "description": "Awesome Blogsite",
5 | "main": "server.js",
6 | "scripts": {
7 | "start": "nodemon server.js",
8 | "dev": "nodemon server.js"
9 | },
10 | "keywords": [],
11 | "author": "Pramit Marattha",
12 | "license": "ISC",
13 | "dependencies": {
14 | "bcrypt": "^5.0.0",
15 | "bcryptjs": "^2.4.3",
16 | "body-parser": "^1.19.0",
17 | "cli-strip-html": "^1.0.2",
18 | "cookie-parser": "^1.4.5",
19 | "cors": "^2.8.5",
20 | "dotenv": "^8.2.0",
21 | "esm": "^3.2.25",
22 | "express": "^4.17.1",
23 | "express-jwt": "^6.0.0",
24 | "express-validator": "^6.9.2",
25 | "formidable": "^1.2.2",
26 | "is-empty": "^1.2.0",
27 | "jsonwebtoken": "^8.5.1",
28 | "lodash": "^4.17.20",
29 | "mongoose": "^5.11.11",
30 | "morgan": "^1.10.0",
31 | "multer": "^1.4.2",
32 | "passport": "^0.4.1",
33 | "passport-jwt": "^4.0.0",
34 | "shortid": "^2.2.16",
35 | "slugify": "^1.4.6",
36 | "string-strip-html": "^7.0.3",
37 | "validator": "^13.5.2"
38 | },
39 | "devDependencies": {
40 | "nodemon": "^2.0.7"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/client/pages/adminDashboard/update/[slug].js:
--------------------------------------------------------------------------------
1 | import Layout from "../../../components/Layout";
2 | import Admin from "../../../components/authentication/Admin";
3 | import UpdateNewBlog from "../../../components/update/UpdateNewBlog";
4 |
5 | import Link from "next/link";
6 | import CategoryIcon from '@material-ui/icons/Category';
7 | import AddIcon from '@material-ui/icons/Add';
8 | import LocalOfferIcon from '@material-ui/icons/LocalOffer';
9 | import BookIcon from '@material-ui/icons/Book';
10 | import PostAddIcon from '@material-ui/icons/PostAdd';
11 | // import MenuIcon from "@material-ui/icons/Menu";
12 | // import BigMenu from "@material-ui/icons/MenuIcons";
13 |
14 |
15 | const Createblog =() =>{
16 | return(
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | Update Blog
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | );
36 | }
37 |
38 | export default Createblog;
--------------------------------------------------------------------------------
/client/pages/adminDashboard/update/blog.js:
--------------------------------------------------------------------------------
1 | import Layout from "../../../components/Layout";
2 | import Admin from "../../../components/authentication/Admin";
3 | import CreateNewBlog from "../../../components/update/NewBlog";
4 |
5 | import Link from "next/link";
6 | import CategoryIcon from '@material-ui/icons/Category';
7 | import AddIcon from '@material-ui/icons/Add';
8 | import LocalOfferIcon from '@material-ui/icons/LocalOffer';
9 | import BookIcon from '@material-ui/icons/Book';
10 | import PostAddIcon from '@material-ui/icons/PostAdd';
11 | // import MenuIcon from "@material-ui/icons/Menu";
12 | // import BigMenu from "@material-ui/icons/MenuIcons";
13 |
14 |
15 |
16 | const Createblog =() =>{
17 | return(
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | Create a new blog
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | );
37 | }
38 |
39 | export default Createblog;
--------------------------------------------------------------------------------
/client/pages/adminDashboard/update/category-tag.js:
--------------------------------------------------------------------------------
1 | import Layout from "../../../components/Layout";
2 | import Admin from "../../../components/authentication/Admin";
3 | import Category from "../../../components/update/Category";
4 | import Tag from "../../../components/update/Tag";
5 | import Link from "next/link";
6 | import CategoryIcon from "@material-ui/icons/Category";
7 | import AddIcon from "@material-ui/icons/Add";
8 | import LocalOfferIcon from "@material-ui/icons/LocalOffer";
9 |
10 | const CategoryTag = () => {
11 | return (
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | Create Catagories and Tags
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | );
34 | };
35 |
36 | export default CategoryTag;
37 |
--------------------------------------------------------------------------------
/client/pages/adminDashboard/update/editBlog.js:
--------------------------------------------------------------------------------
1 | import Layout from "../../../components/Layout";
2 | import Admin from "../../../components/authentication/Admin";
3 | import ReadNewBlog from "../../../components/update/ReadNewBlog";
4 |
5 | import Link from "next/link";
6 | import CategoryIcon from '@material-ui/icons/Category';
7 | import AddIcon from '@material-ui/icons/Add';
8 | import LocalOfferIcon from '@material-ui/icons/LocalOffer';
9 | import BookIcon from '@material-ui/icons/Book';
10 | import PostAddIcon from '@material-ui/icons/PostAdd';
11 | // import MenuIcon from "@material-ui/icons/Menu";
12 | // import BigMenu from "@material-ui/icons/MenuIcons";
13 | import CreateIcon from '@material-ui/icons/Create';
14 |
15 |
16 |
17 | const Editblog =() =>{
18 | return(
19 |
20 |
21 |
22 |
23 |
24 |
25 | Edit Blogs
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | );
37 | }
38 |
39 | export default Editblog;
--------------------------------------------------------------------------------
/server/controllers/user.js:
--------------------------------------------------------------------------------
1 | const User = require("../models/user");
2 | const Blog = require("../models/blogSchema");
3 | const {errorHandler} = require("../helpers/databaseErrorHandler");
4 |
5 |
6 |
7 | exports.profileReadRequest = (req,res) =>{
8 | req.profile.hashed_password = undefined;
9 | return res.json(req.profile);
10 | };
11 |
12 | exports.publicProfileReadRequest=(req,res)=>{
13 | let username = req.params.username
14 | let user;
15 | let blogs;
16 |
17 | User.findOne({username}).exec((err,userFromDB)=>{
18 | if( err || !userFromDB){
19 | return res.status(400).json({
20 | error: "User not found"
21 | })
22 | }
23 | user = userFromDB
24 | Blog.find({postedBy: user._id}).populate("categories","_id name slug").populate("taglists","_id name slug").populate("postedBy","_id name").limit(10).select("_id title slug excerpt categories taglists postedBy createdAt updatedAt")
25 | .exec((err,data) => {
26 | if (err){
27 | return res.status(400).json({
28 | error: errorHandler(err)
29 | })
30 | }
31 | user.photo = undefined
32 | res.json({
33 | user,blogs: data
34 | })
35 | })
36 | })
37 | }
--------------------------------------------------------------------------------
/server/models/blogSchema.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | const { ObjectId } = mongoose.Schema;
3 |
4 | const BlogSchema = new mongoose.Schema(
5 | {
6 | title: {
7 | type: String,
8 | trim: true,
9 | min: 3,
10 | max: 1600,
11 | required: true
12 | },
13 | slug: {
14 | type: String,
15 | unique: true,
16 | index: true
17 | },
18 | body: {
19 | type: {},
20 | required: true,
21 | min: 200,
22 | max: 50000000
23 | },
24 | excerpt: {
25 | type: String,
26 | max: 1000
27 | },
28 | mtitle: {
29 | type: String
30 | },
31 | mdesc: {
32 | type: String
33 | },
34 | photo: {
35 | data: Buffer,
36 | contentType: String
37 | },
38 | categories: [
39 | { type: ObjectId,
40 | ref: 'Category',
41 | required:true,
42 | }],
43 | taglists: [{
44 | type: ObjectId,
45 | ref: 'Tag',
46 | required:true,
47 | }],
48 | postedBy: {
49 | type: ObjectId,
50 | ref: 'Users'
51 | }
52 | },
53 | { timestamps: true }
54 | );
55 |
56 | module.exports = mongoose.model('Blog', BlogSchema);
--------------------------------------------------------------------------------
/client/pages/profile/[username].js:
--------------------------------------------------------------------------------
1 | import Head from 'next/head';
2 | import Link from 'next/link';
3 | import {withRouter} from "next/router";
4 | import Layout from '../../components/Layout';
5 | import { userPublicProfile } from '../../actions/user';
6 | import LabelIcon from '@material-ui/icons/Label';
7 | import CategoryIcon from '@material-ui/icons/Category';
8 | import {API,DOMAIN,APP_NAME} from '../../config';
9 | import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
10 | import moment from 'moment'; //moment js to record date
11 | import React from 'react';
12 |
13 | const UserProfile = ({user,blogs}) => {
14 | return (
15 | <>
16 |
17 |
18 |
19 |
20 |
21 |
22 |
{user.name}
23 |
userinformation
24 |
25 |
26 |
27 |
28 |
29 |
30 | >
31 | )
32 | }
33 |
34 | UserProfile.getInitialProps = ({query})=>{
35 |
36 | return userPublicProfile(query.username).then(data=>{
37 | if(data.error){
38 | console.log(data.error)
39 | } else {
40 | return {user: data.user, blogs: data.blogs}
41 | }
42 | })
43 |
44 | }
45 |
46 | export default UserProfile;
47 |
--------------------------------------------------------------------------------
/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "client",
3 | "version": "1.0.0",
4 | "description": "Tech BlogSite",
5 | "main": "index.js",
6 | "scripts": {
7 | "dev": "next dev",
8 | "build": "next build",
9 | "start": "next start"
10 | },
11 | "author": "pramit marattha",
12 | "license": "ISC",
13 | "dependencies": {
14 | "@fortawesome/fontawesome": "^1.1.8",
15 | "@fortawesome/fontawesome-svg-core": "^1.2.34",
16 | "@fortawesome/free-brands-svg-icons": "^5.15.2",
17 | "@fortawesome/react-fontawesome": "^0.1.14",
18 | "@material-ui/core": "^4.11.2",
19 | "@material-ui/icons": "^4.11.2",
20 | "@zeit/next-css": "^1.0.1",
21 | "@zeit/next-sass": "^1.0.1",
22 | "antd": "^4.11.1",
23 | "axios": "^0.21.1",
24 | "classnames": "^2.2.6",
25 | "framer-motion": "^3.2.2-rc.1",
26 | "informed": "^3.27.0",
27 | "isomorphic-fetch": "^3.0.0",
28 | "js-cookie": "^2.2.1",
29 | "moment": "^2.29.1",
30 | "next": "^10.0.5",
31 | "node-sass": "^5.0.0",
32 | "nprogress": "^0.2.0",
33 | "query-string": "^6.13.8",
34 | "react": "^17.0.1",
35 | "react-dom": "^17.0.1",
36 | "react-modal": "^3.12.1",
37 | "react-quill": "^1.4.0-beta.5",
38 | "react-render-html": "^0.6.0",
39 | "react-reveal": "^1.2.2",
40 | "react-scroll": "^1.8.1",
41 | "react-scroll-progress-bar": "^1.1.12",
42 | "react-transition-group": "^4.4.1",
43 | "react-typical": "^0.1.3",
44 | "reactstrap": "^8.8.1",
45 | "styled-components": "^5.2.1",
46 | "sweetalert2": "^10.13.3"
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/server/server.js:
--------------------------------------------------------------------------------
1 | const morgan = require("morgan");
2 | const express = require("express");
3 | const cookieParser = require("cookie-parser");
4 | const bodyParser = require("body-parser");
5 | const cors = require("cors");
6 | const mongoose = require("mongoose");
7 | require("dotenv").config();
8 | const blogRoutes = require("./routes/blog");
9 | const authRoutes = require("./routes/auth");
10 | const userRoutes = require("./routes/user");
11 | const categoryRoutes = require("./routes/category");
12 | const tagRoutes = require("./routes/tag");
13 |
14 | const app = express();
15 |
16 | const PORT = process.env.PORT || 4000;
17 | const url = process.env.MONGODB_URI;
18 |
19 | mongoose
20 | .connect(url, {
21 | useNewUrlParser: true,
22 | useCreateIndex: true,
23 | useFindAndModify: false,
24 | useUnifiedTopology: true,
25 | })
26 | .then(() => console.log("MongoDatabase Successfully connected"))
27 | .catch((err) => {
28 | console.log(err);
29 | });
30 |
31 | // middlewares
32 | app.use(bodyParser.json());
33 | app.use(cookieParser());
34 | app.use(morgan("dev"));
35 |
36 | if (process.env.NODE_ENV === "development") {
37 | app.use(cors({ origin: `${process.env.CLIENT_URL}` }));
38 | }
39 |
40 | // routes middlewares
41 | app.use("/api", blogRoutes);
42 | app.use("/api", authRoutes);
43 | app.use("/api", userRoutes);
44 | app.use("/api", categoryRoutes);
45 | app.use("/api", tagRoutes);
46 | // cors
47 | app.use(cors());
48 |
49 | app.listen(PORT, (req, res) => {
50 | console.log(`Currently server is running at http://localhost:${PORT}`);
51 | });
52 |
--------------------------------------------------------------------------------
/client/pages/_document.js:
--------------------------------------------------------------------------------
1 | import Document, { Html, Head, Main, NextScript } from 'next/document';
2 |
3 | class MyDocument extends Document {
4 |
5 | render() {
6 | return (
7 |
8 |
9 |
10 |
14 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | )
29 | }
30 | }
31 |
32 | export default MyDocument;
--------------------------------------------------------------------------------
/client/actions/tag.js:
--------------------------------------------------------------------------------
1 | import fetch from "isomorphic-fetch";
2 | import {API} from "../config.js";
3 | import cookie from "js-cookie";
4 |
5 | export const create = (tag,token) => {
6 | return fetch(`${API}/api/tag`, {
7 | method: 'POST',
8 | headers: {
9 | Accept: 'application/json',
10 | 'Content-Type': 'application/json',
11 | Authorization:`Bearer ${token}`
12 |
13 | },
14 | body: JSON.stringify(tag)
15 | })
16 | .then(response => {
17 | return response.json();
18 | })
19 | .catch(error => console.log(error));
20 | };
21 |
22 | export const getTagLists = () => {
23 | return fetch(`${API}/api/taglists`, {
24 | method: 'GET',
25 | })
26 | .then(response => {
27 | return response.json();
28 | })
29 | .catch(error => console.log(error));
30 | };
31 |
32 |
33 | export const getSingleTag = (slug) => {
34 | return fetch(`${API}/api/tag/${slug}`, {
35 | method: 'GET',
36 | })
37 | .then(response => {
38 | return response.json();
39 | })
40 | .catch(error => console.log(error));
41 | };
42 |
43 | export const removeTag= (slug,token) => {
44 | return fetch(`${API}/api/tag/${slug}`, {
45 | method: 'DELETE',
46 | headers: {
47 | Accept: 'application/json',
48 | 'Content-Type': 'application/json',
49 | Authorization:`Bearer ${token}`
50 |
51 | }
52 | })
53 | .then(response => {
54 | return response.json();
55 | })
56 | .catch(error => console.log(error));
57 | };
--------------------------------------------------------------------------------
/client/static/assets/svg/tweet.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/client/actions/category.js:
--------------------------------------------------------------------------------
1 | import fetch from "isomorphic-fetch";
2 | import {API} from "../config.js";
3 | import cookie from "js-cookie";
4 |
5 | export const create = (category,token) => {
6 | return fetch(`${API}/api/category`, {
7 | method: 'POST',
8 | headers: {
9 | Accept: 'application/json',
10 | 'Content-Type': 'application/json',
11 | Authorization:`Bearer ${token}`
12 |
13 | },
14 | body: JSON.stringify(category)
15 | })
16 | .then(response => {
17 | return response.json();
18 | })
19 | .catch(error => console.log(error));
20 | };
21 |
22 | export const getCategories = () => {
23 | return fetch(`${API}/api/categories`, {
24 | method: 'GET',
25 | })
26 | .then(response => {
27 | return response.json();
28 | })
29 | .catch(error => console.log(error));
30 | };
31 |
32 |
33 | export const getSingleCategory = (slug) => {
34 | return fetch(`${API}/api/category/${slug}`, {
35 | method: 'GET',
36 | })
37 | .then(response => {
38 | return response.json();
39 | })
40 | .catch(error => console.log(error));
41 | };
42 |
43 | export const removeCategory = (slug,token) => {
44 | return fetch(`${API}/api/category/${slug}`, {
45 | method: 'DELETE',
46 | headers: {
47 | Accept: 'application/json',
48 | 'Content-Type': 'application/json',
49 | Authorization:`Bearer ${token}`
50 |
51 | }
52 | })
53 | .then(response => {
54 | return response.json();
55 | })
56 | .catch(error => console.log(error));
57 | };
--------------------------------------------------------------------------------
/server/controllers/tag.js:
--------------------------------------------------------------------------------
1 | const Tag = require("../models/tagSchema");
2 | const Blog = require("../models/blogSchema");
3 | const slugify = require("slugify");
4 | const {errorHandler} = require("../helpers/databaseErrorHandler")
5 |
6 | exports.create = (req,res)=>{
7 | const {name} = req.body
8 | let slug = slugify(name).toLowerCase()
9 |
10 | let tag = new Tag({name,slug})
11 |
12 | tag.save((err,data)=>{
13 | if (err){
14 | return res.status(400).json({
15 | error:errorHandler(err)
16 | })
17 | }
18 | res.json(data)
19 | })
20 | };
21 |
22 | exports.list = (req,res)=>{
23 | Tag.find({}).exec((err,data)=>{
24 | if (err){
25 | return res.status(400).json({
26 | error:errorHandler(err)
27 | })
28 | }
29 | res.json(data)
30 | });
31 | };
32 |
33 |
34 | exports.read =(req,res)=>{
35 | const slug = req.params.slug.toLowerCase()
36 |
37 | Tag.findOne({slug}).exec((err,tag)=>{
38 | if (err){
39 | return res.status(400).json({
40 | error:errorHandler(err)
41 | })
42 | }
43 | // res.json(tag)
44 | Blog.find({taglists: tag}).populate('categories','_id name slug').populate('tags','_id name slug').populate('postedBy','_id name').select('_id title slug excerpt categories postedBy taglists createdAt updatedAt').exec((err,data)=>{
45 | if(err){
46 | return res.status(400).json({
47 | error:errorHandler(err)
48 | })
49 | }
50 | res.json({tag: tag, blogs:data})
51 | })
52 | });
53 | };
54 |
55 |
56 |
57 | exports.remove =(req,res)=>{
58 | const slug = req.params.slug.toLowerCase()
59 |
60 | Tag.findOneAndRemove({slug}).exec((err,data)=>{
61 | if (err){
62 | return res.status(400).json({
63 | error:errorHandler(err)
64 | })
65 | }
66 | res.json({
67 | message: "Tag Deleted"
68 | })
69 | });
70 | };
71 |
--------------------------------------------------------------------------------
/client/components/blog/SmallCard/SmallCard.js:
--------------------------------------------------------------------------------
1 | import Head from 'next/head';
2 | import Link from 'next/link';
3 | import renderHTML from 'react-render-html';
4 | import moment from 'moment';
5 | import { API } from '../../../config';
6 | import LabelIcon from '@material-ui/icons/Label';
7 | import CategoryIcon from '@material-ui/icons/Category';
8 | import MoreHorizIcon from '@material-ui/icons/MoreHoriz';
9 | import MoreIcon from '@material-ui/icons/More';
10 | import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
11 |
12 | const SmallCard =({blog})=>{
13 |
14 | console.log(blog);
15 |
16 | return (
17 | <>
18 |
19 |
26 |
27 |
28 |
29 |
30 | {blog.title}
31 |
32 | {renderHTML(blog.excerpt)}
33 |
34 |
35 |
36 |
48 |
49 | >
50 | )};
51 |
52 | export default SmallCard;
--------------------------------------------------------------------------------
/server/controllers/category.js:
--------------------------------------------------------------------------------
1 | const Category = require("../models/categorySchema");
2 | const Blog = require("../models/blogSchema");
3 | const slugify = require("slugify");
4 | const {errorHandler} = require("../helpers/databaseErrorHandler");
5 |
6 | exports.create = (req,res)=>{
7 | const {name} = req.body
8 | let slug = slugify(name).toLowerCase()
9 |
10 | let category = new Category({name,slug})
11 |
12 | category.save((err,data)=>{
13 | if (err){
14 | return res.status(400).json({
15 | error:errorHandler(err)
16 | })
17 | }
18 | res.json(data)
19 | })
20 | };
21 |
22 | exports.list = (req,res)=>{
23 | Category.find({}).exec((err,data)=>{
24 | if (err){
25 | return res.status(400).json({
26 | error:errorHandler(err)
27 | })
28 | }
29 | res.json(data)
30 | });
31 | };
32 |
33 |
34 | exports.read =(req,res)=>{
35 | const slug = req.params.slug.toLowerCase()
36 |
37 | Category.findOne({slug}).exec((err,category)=>{
38 | if (err){
39 | return res.status(400).json({
40 | error:errorHandler(err)
41 | })
42 | }
43 | // res.json(category)
44 | Blog.find({categories: category}).populate('categories','_id name slug').populate('tags','_id name slug').populate('postedBy','_id name').select('_id title slug excerpt categories postedBy taglists createdAt updatedAt').exec((err,data)=>{
45 | if(err){
46 | return res.status(400).json({
47 | error:errorHandler(err)
48 | })
49 | }
50 | res.json({category: category, blogs:data})
51 | })
52 | });
53 | };
54 |
55 |
56 |
57 | exports.remove =(req,res)=>{
58 | const slug = req.params.slug.toLowerCase()
59 |
60 | Category.findOneAndRemove({slug}).exec((err,data)=>{
61 | if (err){
62 | return res.status(400).json({
63 | error:errorHandler(err)
64 | })
65 | }
66 | res.json({
67 | message: "Category Deleted"
68 | })
69 | });
70 | };
71 |
--------------------------------------------------------------------------------
/server/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 | *.lcov
24 |
25 | # nyc test coverage
26 | .nyc_output
27 |
28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29 | .grunt
30 |
31 | # Bower dependency directory (https://bower.io/)
32 | bower_components
33 |
34 | # node-waf configuration
35 | .lock-wscript
36 |
37 | # Compiled binary addons (https://nodejs.org/api/addons.html)
38 | build/Release
39 |
40 | # Dependency directories
41 | node_modules/
42 | jspm_packages/
43 |
44 | # Snowpack dependency directory (https://snowpack.dev/)
45 | web_modules/
46 |
47 | # TypeScript cache
48 | *.tsbuildinfo
49 |
50 | # Optional npm cache directory
51 | .npm
52 |
53 | # Optional eslint cache
54 | .eslintcache
55 |
56 | # Microbundle cache
57 | .rpt2_cache/
58 | .rts2_cache_cjs/
59 | .rts2_cache_es/
60 | .rts2_cache_umd/
61 |
62 | # Optional REPL history
63 | .node_repl_history
64 |
65 | # Output of 'npm pack'
66 | *.tgz
67 |
68 | # Yarn Integrity file
69 | .yarn-integrity
70 |
71 | # dotenv environment variables file
72 | .env
73 | .env.test
74 |
75 | # parcel-bundler cache (https://parceljs.org/)
76 | .cache
77 | .parcel-cache
78 |
79 | # Next.js build output
80 | .next
81 | out
82 |
83 | # Nuxt.js build / generate output
84 | .nuxt
85 | dist
86 |
87 | # Gatsby files
88 | .cache/
89 | # Comment in the public line in if your project uses Gatsby and not Next.js
90 | # https://nextjs.org/blog/next-9-1#public-directory-support
91 | # public
92 |
93 | # vuepress build output
94 | .vuepress/dist
95 |
96 | # Serverless directories
97 | .serverless/
98 |
99 | # FuseBox cache
100 | .fusebox/
101 |
102 | # DynamoDB Local files
103 | .dynamodb/
104 |
105 | # TernJS port file
106 | .tern-port
107 |
108 | # Stores VSCode versions used for testing VSCode extensions
109 | .vscode-test
110 |
111 | # yarn v2
112 | .yarn/cache
113 | .yarn/unplugged
114 | .yarn/build-state.yml
115 | .yarn/install-state.gz
116 | .pnp.*
--------------------------------------------------------------------------------
/server/models/user.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | const crypto = require('crypto');
3 |
4 | const UserSchema = new mongoose.Schema(
5 | {
6 | username: {
7 | type: String,
8 | trim: true,
9 | required: true,
10 | max: 12,
11 | unique: true,
12 | index: true,
13 | lowercase: true
14 | },
15 | name: {
16 | type: String,
17 | trim: true,
18 | required: true,
19 | max: 32
20 | },
21 | email: {
22 | type: String,
23 | trim: true,
24 | required: true,
25 | unique: true,
26 | lowercase: true
27 | },
28 | profile: {
29 | type: String,
30 | required: true
31 | },
32 | hashed_password: {
33 | type: String,
34 | required: true
35 | },
36 | salt: String,
37 | about: {
38 | type: String
39 | },
40 | role: {
41 | type: Number,
42 | default: 0
43 | },
44 | photo: {
45 | data: Buffer,
46 | contentType: String
47 | },
48 | resetPasswordLink: {
49 | data: String,
50 | default: ''
51 | }
52 | },
53 | { timestamps: true }
54 | );
55 |
56 | UserSchema.virtual('password').set(function(password) {
57 | // create a temporarity variable called _password
58 | this._password = password;
59 | // generate salt
60 | this.salt = this.makeSalt();
61 | // encryptPassword
62 | this.hashed_password = this.encryptPassword(password);
63 | }).get(function() {
64 | return this._password;
65 | });
66 |
67 | UserSchema.methods = {
68 | authenticate: function(plainText) {
69 | return this.encryptPassword(plainText) === this.hashed_password;
70 | },
71 |
72 | encryptPassword: function(password) {
73 | if (!password) return '';
74 | try {
75 | return crypto
76 | .createHmac('sha1', this.salt)
77 | .update(password)
78 | .digest('hex');
79 | } catch (err) {
80 | return '';
81 | }
82 | },
83 |
84 | makeSalt: function() {
85 | return Math.round(new Date().valueOf() * Math.random()) + '';
86 | }
87 | };
88 |
89 | module.exports = mongoose.model('Users', UserSchema);
--------------------------------------------------------------------------------
/client/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # next and its enviroment variables
13 | .next
14 | next.config.js
15 |
16 | # Runtime data
17 | pids
18 | *.pid
19 | *.seed
20 | *.pid.lock
21 |
22 | # Directory for instrumented libs generated by jscoverage/JSCover
23 | lib-cov
24 |
25 | # Coverage directory used by tools like istanbul
26 | coverage
27 | *.lcov
28 |
29 | # nyc test coverage
30 | .nyc_output
31 |
32 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
33 | .grunt
34 |
35 | # Bower dependency directory (https://bower.io/)
36 | bower_components
37 |
38 | # node-waf configuration
39 | .lock-wscript
40 |
41 | # Compiled binary addons (https://nodejs.org/api/addons.html)
42 | build/Release
43 |
44 | # Dependency directories
45 | node_modules/
46 | jspm_packages/
47 |
48 | # Snowpack dependency directory (https://snowpack.dev/)
49 | web_modules/
50 |
51 | # TypeScript cache
52 | *.tsbuildinfo
53 |
54 | # Optional npm cache directory
55 | .npm
56 |
57 | # Optional eslint cache
58 | .eslintcache
59 |
60 | # Microbundle cache
61 | .rpt2_cache/
62 | .rts2_cache_cjs/
63 | .rts2_cache_es/
64 | .rts2_cache_umd/
65 |
66 | # Optional REPL history
67 | .node_repl_history
68 |
69 | # Output of 'npm pack'
70 | *.tgz
71 |
72 | # Yarn Integrity file
73 | .yarn-integrity
74 |
75 | # dotenv environment variables file
76 | .env
77 | .env.test
78 |
79 | # parcel-bundler cache (https://parceljs.org/)
80 | .cache
81 | .parcel-cache
82 |
83 | # Next.js build output
84 | .next
85 | out
86 |
87 | # Nuxt.js build / generate output
88 | .nuxt
89 | dist
90 |
91 | # Gatsby files
92 | .cache/
93 | # Comment in the public line in if your project uses Gatsby and not Next.js
94 | # https://nextjs.org/blog/next-9-1#public-directory-support
95 | # public
96 |
97 | # vuepress build output
98 | .vuepress/dist
99 |
100 | # Serverless directories
101 | .serverless/
102 |
103 | # FuseBox cache
104 | .fusebox/
105 |
106 | # DynamoDB Local files
107 | .dynamodb/
108 |
109 | # TernJS port file
110 | .tern-port
111 |
112 | # Stores VSCode versions used for testing VSCode extensions
113 | .vscode-test
114 |
115 | # yarn v2
116 | .yarn/cache
117 | .yarn/unplugged
118 | .yarn/build-state.yml
119 | .yarn/install-state.gz
120 | .pnp.*
--------------------------------------------------------------------------------
/client/pages/adminDashboard/index.js:
--------------------------------------------------------------------------------
1 | import Layout from "../../components/Layout";
2 | import Admin from "../../components/authentication/Admin";
3 | import Link from "next/link";
4 | import LabelIcon from '@material-ui/icons/Label';
5 | import CategoryIcon from '@material-ui/icons/Category';
6 | import BookIcon from '@material-ui/icons/Book';
7 |
8 |
9 |
10 |
11 | const AdminIndex =() =>{
12 | return(
13 |
14 |
15 |
16 |
17 |
18 |
Admin Dashboard
19 |
20 |
21 | {/*
*/}
22 | {/* // new category */}
23 | {/* */}
24 |
25 |
26 |
Create new category
27 |
28 |
29 | {/* */}
30 |
31 | {/* // new tag */}
32 | {/* */}
33 |
34 |
35 |
Create new tag
36 |
37 |
38 | {/* */}
39 |
40 | {/* // new blog */}
41 | {/* */}
42 |
43 |
44 |
Create new blog
45 |
46 |
47 | {/* */}
48 | {/* */}
49 |
50 |
51 |
52 |
Update blogs
53 |
54 |
55 |
56 | {/*
57 | Right
58 |
*/}
59 |
60 |
61 |
62 |
63 | )
64 | }
65 |
66 | export default AdminIndex;
--------------------------------------------------------------------------------
/client/components/LandingPage/Navbar.js:
--------------------------------------------------------------------------------
1 | // import '../styles/index.css'
2 | import React, { useState } from 'react';
3 | // import '../styles/Navbar.css'
4 | import Link from 'next/link';
5 | import MenuOpenIcon from '@material-ui/icons/MenuOpen';
6 |
7 | const Navbar = () => {
8 | const [stateBurger, setStateBurger] = useState('')
9 | const [activeMenu, setActiveMenu] = useState('')
10 | const [closeMenu, setCloseMenu] = useState('')
11 |
12 | const toggleMenu = () => {
13 | if (stateBurger === 'active') {
14 | setStateBurger('')
15 | setActiveMenu('')
16 | setCloseMenu('')
17 | } else {
18 | setStateBurger('active')
19 | setActiveMenu('menu-active')
20 | setCloseMenu('active-close')
21 | }
22 | }
23 |
24 | return (
25 | <>
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
71 |
72 | Tech Blogsite
73 |
74 |
75 |
76 |
77 | Sign-up
78 |
79 |
80 |
81 |
82 | login
83 |
84 |
85 |
86 |
87 |
88 |
89 | >
90 | )
91 | }
92 |
93 | export default Navbar
94 |
--------------------------------------------------------------------------------
/client/components/LandingPage/Footer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Link from 'next/link';
3 | import BookIcon from '@material-ui/icons/Book';
4 | import DoubleArrowIcon from '@material-ui/icons/DoubleArrow';
5 | import RssFeedIcon from '@material-ui/icons/RssFeed';
6 |
7 |
8 | const Footer = () => {
9 | return (
10 |
11 |
12 |
24 |
25 |
26 | Get Connected
27 |
28 |
50 |
51 |
52 |
53 |
54 | Let's Get Started
55 |
56 |
57 |
58 |
59 | Register
60 |
61 |
62 |
63 |
64 | Login
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | Recent Blogposts
74 |
75 |
76 |
77 |
78 | Blogs
79 |
80 |
81 |
82 |
83 |
84 |
85 |
88 |
89 |
90 |
91 | )
92 | }
93 |
94 | export default Footer
95 |
--------------------------------------------------------------------------------
/client/components/blog/Search/Search.js:
--------------------------------------------------------------------------------
1 | import React,{useState,useEffect} from 'react';
2 | import Head from 'next/head';
3 | import Link from 'next/link';
4 | import renderHTML from 'react-render-html';
5 | import moment from 'moment';
6 | import { API } from '../../../config';
7 | import LabelIcon from '@material-ui/icons/Label';
8 | import CategoryIcon from '@material-ui/icons/Category';
9 | import MoreHorizIcon from '@material-ui/icons/MoreHoriz';
10 | import MoreIcon from '@material-ui/icons/More';
11 | import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
12 | import {listSearchBlogItems} from "../../../actions/blog"
13 |
14 | const Search = () => {
15 | const [infos,setInfos] = useState({
16 | search: undefined,
17 | results:[],
18 | searched:false ,
19 | message:''
20 | })
21 | const {search,results,searched,message} = infos
22 |
23 | const searchSubmit = event=>{
24 | event.preventDefault();
25 | listSearchBlogItems({search}).then(data=>{
26 | setInfos({...infos,results:data,searched:true,message:`Total ${data.length} blogs found`})
27 | })
28 | };
29 |
30 | const handleChange = event =>{
31 | // event.preventDefault();
32 | setInfos({...infos,search: event.target.value,searched:false,results:[]})
33 | };
34 |
35 | const searchedBloglists =(results)=>{
36 | return (
37 |
38 | {message &&
{message}
}
39 | {results.map((blog,index)=>(
40 |
45 | ))}
46 |
47 | )
48 | }
49 |
50 | const searchForm = ()=>{
51 | return (
52 |
62 | )
63 | };
64 |
65 |
66 | return (
67 |
68 |
69 | {searchForm()}
70 |
71 | {searched &&
{searchedBloglists(results)}
}
72 |
73 | )
74 | }
75 |
76 | export default Search;
77 |
--------------------------------------------------------------------------------
/client/pages/taglists/[slug].js:
--------------------------------------------------------------------------------
1 | import Head from 'next/head';
2 | import Link from 'next/link';
3 | import {withRouter} from "next/router";
4 | import Layout from '../../components/Layout';
5 | import { getSingleTag } from '../../actions/tag';
6 | import LabelIcon from '@material-ui/icons/Label';
7 | import CategoryIcon from '@material-ui/icons/Category';
8 | import {API,DOMAIN,APP_NAME} from '../../config';
9 | import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
10 | import moment from 'moment';
11 | import renderHTML from 'react-render-html';
12 | import Card from "../../components/blog/Card";
13 |
14 |
15 |
16 | const Tag = ({tag,blogs,query})=>{
17 | console.log(tag.name)
18 | const head = ()=>(
19 |
20 | {tag.name} | {APP_NAME}
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | );
33 |
34 | return (
35 | <>
36 | {head()}
37 |
38 |
39 |
55 |
56 |
57 | >
58 | )
59 | };
60 |
61 | Tag.getInitialProps = ({query}) =>{
62 | return getSingleTag(query.slug).then(data=>{
63 | if(data.error){
64 | console.log(data.error)
65 | } else {
66 | return {tag: data.tag, blogs:data.blogs,query}
67 | }
68 | })
69 | };
70 |
71 | export default Tag;
--------------------------------------------------------------------------------
/client/components/blog/Card/Card.js:
--------------------------------------------------------------------------------
1 | import Head from 'next/head';
2 | import Link from 'next/link';
3 | import renderHTML from 'react-render-html';
4 | import moment from 'moment';
5 | import { API } from '../../../config';
6 | import LabelIcon from '@material-ui/icons/Label';
7 | import CategoryIcon from '@material-ui/icons/Category';
8 | import MoreHorizIcon from '@material-ui/icons/MoreHoriz';
9 | import MoreIcon from '@material-ui/icons/More';
10 | import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
11 |
12 |
13 | const Card =({blog})=>{
14 |
15 | const listAndDisplayAllCategories = blog => {
16 | return (
17 | blog.categories.map((cat,index)=>{
18 | return (
19 |
20 | {cat.name}
21 |
22 | )
23 | })
24 | )
25 | };
26 |
27 | const listAndDisplayAllTaglists = blog => {
28 | return (
29 | blog.taglists.map((tagg,index)=>{
30 | return (
31 |
32 | {tagg.name}
33 |
34 | )
35 | })
36 | )
37 | };
38 |
39 | return (
40 | <>
41 |
42 |
47 |
48 | {listAndDisplayAllCategories(blog)} {listAndDisplayAllTaglists(blog)}
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | {renderHTML(blog.excerpt)}
59 | Read more
60 |
61 |
62 |
63 |
64 |
65 |
Author : {blog.postedBy.name} | Published {moment(blog.updatedAt).fromNow()}
66 |
67 |
68 |
69 |
70 | >
71 | )};
72 |
73 | export default Card;
--------------------------------------------------------------------------------
/client/pages/categories/[slug].js:
--------------------------------------------------------------------------------
1 | import Head from 'next/head';
2 | import Link from 'next/link';
3 | import {withRouter} from "next/router";
4 | import Layout from '../../components/Layout';
5 | import { getSingleCategory } from '../../actions/category';
6 | import LabelIcon from '@material-ui/icons/Label';
7 | import CategoryIcon from '@material-ui/icons/Category';
8 | import {API,DOMAIN,APP_NAME} from '../../config';
9 | import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
10 | import moment from 'moment';
11 | import renderHTML from 'react-render-html';
12 | import Card from "../../components/blog/Card";
13 |
14 |
15 |
16 | const Category = ({category,blogs,query})=>{
17 | console.log(category.name)
18 | const head = ()=>(
19 |
20 | {category.name} | {APP_NAME}
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | );
33 |
34 | return (
35 | <>
36 | {head()}
37 |
38 |
39 |
55 |
56 |
57 | >
58 | )
59 | };
60 |
61 | Category.getInitialProps = ({query}) =>{
62 | return getSingleCategory(query.slug).then(data=>{
63 | if(data.error){
64 | console.log(data.error)
65 | } else {
66 | return {category: data.category, blogs:data.blogs,query}
67 | }
68 | })
69 | };
70 |
71 | export default Category;
--------------------------------------------------------------------------------
/client/actions/authentication.js:
--------------------------------------------------------------------------------
1 | import fetch from "isomorphic-fetch";
2 | import {API} from "../config.js";
3 | import cookie from "js-cookie";
4 |
5 | export const signup = (user) => {
6 | return fetch(`${API}/api/signup`, {
7 | method: 'POST',
8 | headers: {
9 | Accept: 'application/json',
10 | 'Content-Type': 'application/json'
11 | },
12 | body: JSON.stringify(user)
13 | })
14 | .then(response => {
15 | return response.json();
16 | })
17 | .catch(error => console.log(error));
18 | };
19 |
20 | export const login = (user) => {
21 | return fetch(`${API}/api/login`, {
22 | method: 'POST',
23 | headers: {
24 | Accept: 'application/json',
25 | 'Content-Type': 'application/json'
26 | },
27 | body: JSON.stringify(user)
28 | })
29 | .then(response => {
30 | return response.json();
31 | })
32 | .catch(error => console.log(error));
33 | };
34 |
35 | export const logout =(next) =>{
36 | removeCookie('token')
37 | removeLocalStorage('user')
38 | next();
39 |
40 | return fetch(`${API}/api/logout`,{
41 | method: "GET"
42 | }).then(response =>{
43 | console.log("Successfully Logged Out")
44 | }).catch(err=> console.log(err))
45 | }
46 |
47 | // setting the cookie
48 | export const setCookie = (key,value)=>{
49 | if(process.browser){
50 | cookie.set(key,value,{
51 | expires:365
52 | });
53 | }
54 | };
55 |
56 | // removing the cookie
57 | export const removeCookie = (key)=>{
58 | if(process.browser){
59 | cookie.remove(key,{
60 | expires:365
61 | });
62 | }
63 | };
64 |
65 | // getting the cookie
66 | export const getCookie = (key)=>{
67 | if(process.browser){
68 | return cookie.get(key);
69 | }
70 | };
71 |
72 | // setting up localstorage
73 | export const setLocalStorage = (key,value) =>{
74 | if(process.browser){
75 | localStorage.setItem(key,JSON.stringify(value))
76 | }
77 | };
78 |
79 | // removing localstorage
80 | export const removeLocalStorage = (key) =>{
81 | if(process.browser){
82 | localStorage.removeItem(key)
83 | }
84 | };
85 |
86 | // authentication of user by passing data to cookie as well as localstorage
87 | export const authenticate = (data,next)=>{ // takes two argument one is data and another one is call back argument
88 | setCookie('token',data.token);
89 | setLocalStorage('user',data.user);
90 | next();
91 | }
92 |
93 | // if user is authenticated
94 | export const isAuthenticated =()=>{
95 | if(process.browser){
96 | const checkingCookie = getCookie('token')
97 | if (checkingCookie){
98 | if(localStorage.getItem('user')){
99 | return JSON.parse(localStorage.getItem('user'))
100 | } else {
101 | return false;
102 | }
103 | }
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/client/components/authentication/Register.js:
--------------------------------------------------------------------------------
1 | import React,{useState,useEffect} from 'react';
2 | import {signup,isAuthenticated} from "../../actions/authentication.js";
3 | import Router from "next/router";
4 | import PersonAddIcon from '@material-ui/icons/PersonAdd';
5 |
6 |
7 |
8 | const Register = () => {
9 |
10 | const [info,setInfo] = useState({name:"",email:"",password:"",error:"",loading:false,message:"",showForm:true});
11 |
12 | const {name,email,password,error,loading,message,showForm} = info
13 |
14 | useEffect(()=>{
15 | isAuthenticated() && Router.push(`/`)
16 | },[])
17 |
18 | const handleSubmit = event => {
19 | event.preventDefault();
20 | // console.table({ name, email, password, error, loading, message, showForm });
21 | setInfo({ ...info, loading:true, error:false });
22 | const user = { name, email, password };
23 |
24 | signup(user).then(data => {
25 |
26 | // console.log("data.error",data.name)
27 |
28 | if (data.error) {
29 | setInfo({ ...info, error: data.error, loading: false });
30 | } else {
31 | setInfo({...info,name: '',email: '',password: '',error: '',loading: false,message: data.message,showForm: false});
32 | }
33 | });
34 | };
35 |
36 |
37 | const handleChange= name =>(event)=>{
38 | setInfo({...info,error:false,[name]: event.target.value});
39 | }
40 |
41 | const showLoading = () =>(loading ? Loading.....
: "");
42 | const showError = () =>(error ? {error}
: "");
43 |
44 | const showMessage = () =>(message ? {message}
: "");
45 |
46 | const registerForm=()=>{
47 | return (
48 |
49 |
68 | )
69 | };
70 |
71 | return (
72 | <>
73 | {showError()}
74 | {showLoading()}
75 | {showMessage()}
76 | {showForm && registerForm()}
77 | >
78 | )
79 | }
80 |
81 | export default Register
82 |
--------------------------------------------------------------------------------
/client/components/authentication/LoginAuth.js:
--------------------------------------------------------------------------------
1 | import React,{useState,useEffect} from 'react';
2 | import {login,authenticate,isAuthenticated} from "../../actions/authentication.js";
3 | import Router from "next/router";
4 | import VpnKeyIcon from '@material-ui/icons/VpnKey';
5 |
6 | const LoginAuth = () => {
7 |
8 | const [info,setInfo] = useState({email:"",password:"",error:"",loading:false,message:"",showForm:true});
9 |
10 | const {email,password,error,loading,message,showForm} = info
11 |
12 |
13 | useEffect(()=>{
14 | isAuthenticated() && Router.push(`/`)
15 | },[])
16 |
17 | const handleSubmit = event => {
18 | event.preventDefault();
19 | // console.table({ name, email, password, error, loading, message, showForm });
20 | setInfo({ ...info, loading:true, error:false });
21 | const user = {email, password };
22 |
23 | login(user).then(data => {
24 |
25 | // console.log("data.error",data.name)
26 |
27 | if (data.error) {
28 | setInfo({ ...info, error: data.error, loading: false });
29 | } else {
30 | // user token to cookie
31 | // user information to localstorage
32 | // authenticate the user
33 | authenticate(data,()=>{
34 | if(isAuthenticated() && isAuthenticated().role === 1){
35 | Router.push(`/adminDashboard`)
36 | } else {
37 | Router.push(`/userDashboard`)
38 | }
39 | })
40 |
41 | }
42 | });
43 | };
44 |
45 | const handleChange= name =>(event)=>{
46 | setInfo({...info,error:false,[name]: event.target.value});
47 | }
48 |
49 | const showLoading = () =>(loading ? Loading.....
: "");
50 | const showError = () =>(error ? {error}
: "");
51 |
52 | const showMessage = () =>(message ? {message}
: "");
53 |
54 | const loginForm=()=>{
55 | return (
56 |
57 |
71 | )
72 | };
73 |
74 | return (
75 | <>
76 | {showError()}
77 | {showLoading()}
78 | {showMessage()}
79 | {showForm && loginForm()}
80 | >
81 | )
82 | }
83 |
84 | export default LoginAuth;
85 |
--------------------------------------------------------------------------------
/client/actions/blog.js:
--------------------------------------------------------------------------------
1 | import fetch from "isomorphic-fetch";
2 | import { API } from "../config.js";
3 | import queryString from "query-string";
4 |
5 | export const createBlog = (blog,token) => {
6 | return fetch(`${API}/api/blog`, {
7 | method: 'POST',
8 | headers: {
9 | Accept: 'application/json',
10 | Authorization:`Bearer ${token}`
11 |
12 | },
13 | body: blog
14 | })
15 | .then(response => {
16 | return response.json();
17 | })
18 | .catch(error => console.log(error));
19 | };
20 |
21 | export const listBlogsWithCategoriesAndTaglists = (skip,limit) => {
22 | const data ={
23 | limit,skip
24 | }
25 | return fetch(`${API}/api/bloglists-categories-taglists`, {
26 | method: 'POST',
27 | headers: {
28 | Accept: 'application/json',
29 | 'Content-Type':'application/json'
30 | },
31 | body: JSON.stringify(data)
32 | }).then(response => {
33 | return response.json();
34 | }).catch(error => console.log(error));
35 | };
36 |
37 |
38 | export const singleBlog = slug =>{
39 | return fetch(`${API}/api/blog/${slug}`,{
40 | method: 'GET'
41 | }).then(response=>{
42 | return response.json()
43 | }).catch(err => console.log(err))
44 | };
45 |
46 | export const blogListRelated = (blog) => {
47 | return fetch(`${API}/api/bloglists/related`, {
48 | method: 'POST',
49 | headers: {
50 | Accept: 'application/json',
51 | 'Content-Type':'application/json'
52 | },
53 | body: JSON.stringify(blog)
54 | }).then(response => {
55 | return response.json();
56 | }).catch(error => console.log(error));
57 | };
58 |
59 | export const listingTheBlog = slug =>{
60 | return fetch(`${API}/api/bloglists`,{
61 | method: 'GET'
62 | }).then(response=>{
63 | return response.json()
64 | }).catch(err => console.log(err))
65 | };
66 |
67 | export const removingTheBlog = (slug,token) => {
68 | return fetch(`${API}/api/blog/${slug}`, {
69 | method: 'DELETE',
70 | headers: {
71 | Accept: 'application/json',
72 | 'Content-Type': 'application/json',
73 | Authorization:`Bearer ${token}`
74 |
75 | },
76 | })
77 | .then(response => {
78 | return response.json();
79 | })
80 | .catch(error => console.log(error));
81 | };
82 |
83 | export const updatingTheBlog = (blog,token,slug) => {
84 | return fetch(`${API}/api/blog/${slug}`, {
85 | method: 'PUT',
86 | headers: {
87 | Accept: 'application/json',
88 | Authorization:`Bearer ${token}`
89 |
90 | },
91 | body: blog
92 | })
93 | .then(response => {
94 | return response.json();
95 | })
96 | .catch(error => console.log(error));
97 | };
98 |
99 |
100 | export const listSearchBlogItems = params =>{
101 | let query = queryString.stringify(params)
102 | return fetch(`${API}/api/bloglists/search?${query}`,{
103 | method: 'GET'
104 | }).then(response=>{
105 | return response.json()
106 | }).catch(err => console.log(err))
107 | };
--------------------------------------------------------------------------------
/client/components/update/ReadNewBlog.js:
--------------------------------------------------------------------------------
1 | import Link from "next/link";
2 | import React,{useState,useEffect} from "react";
3 | import Router from "next/router";
4 | // importing actions
5 | import {getCookie,isAuthenticated} from "../../actions/authentication";
6 | import {listingTheBlog,removingTheBlog,updatingTheBlog} from "../../actions/blog";
7 | import PublishIcon from '@material-ui/icons/Publish';
8 | import {ReactQuillModules,ReactQuillFormats} from "../../helpers/ReactQuill";
9 | import DeleteForeverIcon from '@material-ui/icons/DeleteForever';
10 | import moment from "moment";
11 | import CreateIcon from '@material-ui/icons/Create';
12 |
13 | const ReadNewBlog = () => {
14 | const [blogs,setBlogs] = useState([])
15 | const [message,setMessage] = useState('')
16 | const token = getCookie('token')
17 |
18 | const loadBlogs = ()=>{
19 | listingTheBlog().then(data=>{
20 | if(data.error){
21 | console.log(data.error)
22 | } else{
23 | setBlogs(data)
24 | }
25 | })
26 | }
27 | useEffect(()=>{
28 | loadBlogs();
29 | },[])
30 |
31 | const deleteTheBlog =(slug)=>{
32 | removingTheBlog(slug,token).then(data=>{
33 | if(data.error){
34 | console.log(data.error)
35 | } else{
36 | setMessage(data.message)
37 | loadBlogs();
38 | }
39 | })
40 | }
41 |
42 | const deleteConfirmation =()=>{
43 | let answer = window.confirm("Are you sure you want to delete this ?")
44 | if (answer){
45 | deleteTheBlog(slug)
46 | }
47 | };
48 |
49 | const showEditButton= blog =>{
50 | if (isAuthenticated() && isAuthenticated().role === 0){
51 | return (
52 |
53 | Edit
54 |
55 | );
56 | } else if (isAuthenticated() && isAuthenticated().role === 1){
57 | return (
58 |
59 | Edit
60 |
61 | )
62 | }
63 | };
64 |
65 | const showingAllBlogs=()=>{
66 | return blogs.map((blog,index)=>{
67 | return (
68 |
69 |
{blog.title}
70 |
Author : {blog.postedBy.name} | Published {moment(blog.updatedAt).fromNow()}
71 |
deleteConfirmation(blog.slug)}> Delete
72 | {showEditButton(blog)}
73 | {/* {console.log("role",isAuthenticated().role)} */}
74 |
75 | )
76 | })
77 | }
78 |
79 | return (
80 | <>
81 |
82 |
83 |
84 | {message &&
{message}
}
85 | {showingAllBlogs()}
86 |
87 |
88 |
89 | >
90 | )
91 | };
92 |
93 | export default ReadNewBlog;
94 |
--------------------------------------------------------------------------------
/server/controllers/userAuthentication.js:
--------------------------------------------------------------------------------
1 | const User = require("../models/user");
2 | const shortId = require("shortid");
3 | const expressJwt = require("express-jwt");
4 | const jwt = require("jsonwebtoken")
5 |
6 |
7 | exports.signup = (req,res)=>{
8 | // const {name,email,password} = req.body
9 | // res.json({
10 | // user:{name,email,password}
11 | // });
12 | // if user exist
13 |
14 | User.findOne({email: req.body.email}).exec((err,user)=>{
15 | if(user){
16 | return res.status(400).json({
17 | error: "Email already exists"
18 | })
19 | }
20 | // if not
21 | const {name,email,password} = req.body
22 | let username = shortId.generate()
23 | let profile = `${process.env.CLIENT_URL}/profile/${username}`
24 |
25 | // create a new user
26 | let newUser = new User({name,email,password,profile,username})
27 | // save that user
28 | newUser.save((err,success)=>{
29 | if(err){
30 | return res.status(400).json({
31 | error:err
32 | });
33 | };
34 | res.json({
35 | message: "Completed Signup process.Please Login to continue"
36 | });
37 | // res.json({
38 | // user: success
39 | // })
40 | });
41 | });
42 | };
43 |
44 |
45 | exports.login = (req,res)=>{
46 | const {email,password} = req.body
47 | //checking user existence
48 | User.findOne({email}).exec((err,user)=>{
49 | if(err || !user){
50 | return res.status(400).json({
51 | error: "You forgot to 'SignUp'.Email does not exist"
52 | });
53 | }
54 | // authenticate the user
55 | if(!user.authenticate(password)){
56 | return res.status(400).json({
57 | error: "Email and password does not match"
58 | });
59 | }
60 | // token generation
61 | const token = jwt.sign({_id: user._id},process.env.JWT_TOKEN_SECRET,{expiresIn: "365d"})
62 |
63 | res.cookie("token",token,{expiresIn:"365d"})
64 | const {_id,username,name,email,role} = user
65 | return res.json({
66 | token,
67 | user:{_id,username,name,email,role}
68 | })
69 | });
70 |
71 | };
72 |
73 | // signout and require login middlewares for protecting routes
74 |
75 | exports.logout = (req,res)=>{
76 | res.clearCookie("token")
77 | res.json({
78 | message: "Successfully logged out"
79 | });
80 | };
81 |
82 |
83 | exports.requireLogin = expressJwt({
84 | secret: process.env.JWT_TOKEN_SECRET,
85 | algorithms: ['HS256']
86 | });
87 |
88 |
89 | exports.authenticationMiddleware = (req,res,next)=>{
90 | const authenticateUserId = req.user._id
91 | User.findById({_id: authenticateUserId}).exec((err,user)=>{
92 | if (err || !user){
93 | return res.status(400).json({
94 | error:"User not found"
95 | })
96 | }
97 | req.profile = user
98 | next()
99 | })
100 | };
101 |
102 |
103 | exports.adminAuthenticationMiddleware = (req,res,next)=>{
104 | const authenticateAdminUserId = req.user._id
105 | User.findById({_id: authenticateAdminUserId}).exec((err,user)=>{
106 | if (err || !user){
107 | return res.status(400).json({
108 | error:"User not found"
109 | })
110 | }
111 | if (user.role !== 1){
112 | return res.status(400).json({
113 | error:"Access Denied!. Only authorized for admins"
114 | });
115 | }
116 | req.profile = user
117 | next()
118 | })
119 | };
--------------------------------------------------------------------------------
/client/components/update/Category.js:
--------------------------------------------------------------------------------
1 | import React,{useState,useEffect} from "react";
2 | import Link from "next/link";
3 | import Router from "next/router";
4 | import {isAuthenticated,getCookie} from "../../actions/authentication";
5 | import {create,getCategories,removeCategory} from "../../actions/category";
6 | import CategoryIcon from '@material-ui/icons/Category';
7 | import AddIcon from '@material-ui/icons/Add';
8 |
9 |
10 | const Category =()=>{
11 | const [infos,setInfos] = useState({name:"",error:false,success:false,categories:[],removed:false,reload:false});
12 |
13 | const {name,error,success,categories,removed,reload} = infos
14 | const token = getCookie("token")
15 |
16 | const processCategories = ()=>{
17 | getCategories().then(data=>{
18 | if (data.error){
19 | console.log(data.error)
20 | }else{
21 | setInfos({...infos,categories: data})
22 | }
23 | });
24 | };
25 |
26 | const removeTheSelectedCategory = (slug) =>{
27 | removeCategory(slug,token).then(data =>{
28 | if (data.error){
29 | console.log(data.error)
30 | }else{
31 | setInfos({...infos,name:"",error:false,success:false,removed:!removed,reload:!reload})
32 | }
33 | })
34 | }
35 |
36 | const doubleClickDelete = (slug)=>{
37 | let confirmation = window.confirm("Delete this category ?")
38 | if (confirmation){
39 | removeTheSelectedCategory(slug)
40 | }
41 | };
42 | // console.log("ahahahahaha",categories)
43 | const loopingThroughCategories =()=>{
44 | return categories.map((cat,index)=>{
45 | return (
46 | doubleClickDelete(cat.slug)} title="Double Tap to delete" key={index} className="btn btn-outline-info mr-1 ml-1 mt-4">{cat.name}
47 | )
48 | });
49 | };
50 |
51 |
52 |
53 | useEffect(()=>{
54 | processCategories();
55 | },[reload])
56 |
57 | const handleSubmit =(event)=>{
58 | event.preventDefault()
59 | // console.log("create",name)
60 | create({name},token).then(data=>{
61 | if(data.error){
62 | setInfos({...infos,error:data.error,success:false})
63 | } else{
64 | setInfos({...infos,name:"",error:false,success:false,removed:!removed,reload:!reload})
65 | }
66 | });
67 | };
68 |
69 |
70 | const handleChange =(event)=>{
71 | setInfos({...infos,name:event.target.value, error:false, success:true, removed:""})
72 | }
73 |
74 | // const createdCategorySuccessfully = ()=>{
75 | // if (success){
76 | // return (
77 | // Category Created Successfully
78 | // )
79 | // };
80 | // };
81 |
82 | const errorCreatingCategory = ()=>{
83 | if (error){
84 | return (
85 | Duplicate Catagory
86 | )
87 | };
88 | };
89 |
90 | const removedCategorySuccessfully =()=>{
91 | if (removed){
92 | return (
93 | Category Deleted Successfully
94 | )
95 | };
96 | };
97 |
98 | const mouseHndler =(event)=>{
99 | setInfos({...infos,error:false,success:false,removed:""})
100 | }
101 |
102 |
103 | const newCategoryForm =()=>(
104 |
121 | );
122 |
123 | return (
124 | <>
125 | {/* {createdCategorySuccessfully()} */}
126 | {errorCreatingCategory()}
127 | {/* {removedCategorySuccessfully()} */}
128 |
129 |
130 | {newCategoryForm()}
131 | {loopingThroughCategories()}
132 |
133 | >
134 | );
135 | };
136 |
137 | export default Category;
--------------------------------------------------------------------------------
/client/components/update/Tag.js:
--------------------------------------------------------------------------------
1 | import React,{useState,useEffect} from "react";
2 | import Link from "next/link";
3 | import Router from "next/router";
4 | import {isAuthenticated,getCookie} from "../../actions/authentication";
5 | import {create,getTagLists,removeTag} from "../../actions/tag";
6 | import CategoryIcon from '@material-ui/icons/Category';
7 | import AddIcon from '@material-ui/icons/Add';
8 | import LocalOfferIcon from '@material-ui/icons/LocalOffer';
9 |
10 |
11 | const Tag =()=>{
12 | const [infos,setInfos] = useState({name:"",error:false,success:false,taglists:[],removed:false,reload:false});
13 |
14 | const {name,error,success,taglists,removed,reload} = infos
15 | const token = getCookie("token")
16 |
17 | const processTagLists = ()=>{
18 | getTagLists().then(data=>{
19 | if (data.error){
20 | console.log(data.error)
21 | }else{
22 | setInfos({...infos,taglists: data})
23 | }
24 | });
25 | };
26 |
27 | const removeTheSelectedTag = (slug) =>{
28 | removeTag(slug,token).then(data =>{
29 | if (data.error){
30 | console.log(data.error)
31 | }else{
32 | setInfos({...infos,name:"",error:false,success:false,removed:!removed,reload:!reload})
33 | }
34 | })
35 | }
36 |
37 | const doubleClickDelete = (slug)=>{
38 | let confirmation = window.confirm("Delete this Tag ?")
39 | if (confirmation){
40 | removeTheSelectedTag(slug)
41 | }
42 | };
43 | // console.log("ahahahahaha",categories)
44 | const loopingThroughTagLists =()=>{
45 | return taglists.map((tagg,index)=>{
46 | return (
47 | doubleClickDelete(tagg.slug)} title="Double Tap to delete tag" key={index} className="btn btn-outline-primary mr-1 ml-1 mt-4">{tagg.name}
48 | )
49 | });
50 | };
51 |
52 |
53 |
54 | useEffect(()=>{
55 | processTagLists();
56 | },[reload])
57 |
58 | const handleSubmit =(event)=>{
59 | event.preventDefault()
60 | // console.log("create",name)
61 | create({name},token).then(data=>{
62 | if(data.error){
63 | setInfos({...infos,error:data.error,success:false})
64 | } else{
65 | setInfos({...infos,name:"",error:false,success:false,removed:!removed,reload:!reload})
66 | }
67 | });
68 | };
69 |
70 |
71 | const handleChange =(event)=>{
72 | setInfos({...infos,name:event.target.value, error:false, success:true, removed:""})
73 | }
74 |
75 | // const createdCategorySuccessfully = ()=>{
76 | // if (success){
77 | // return (
78 | // Category Created Successfully
79 | // )
80 | // };
81 | // };
82 |
83 | const errorCreatingTag = ()=>{
84 | if (error){
85 | return (
86 | Duplicate Tag
87 | )
88 | };
89 | };
90 |
91 | // const removedCategorySuccessfully =()=>{
92 | // if (removed){
93 | // return (
94 | // Category Deleted Successfully
95 | // )
96 | // };
97 | // };
98 |
99 | const mouseHndler =(event)=>{
100 | setInfos({...infos,error:false,success:false,removed:""})
101 | }
102 |
103 |
104 | const newTagForm =()=>(
105 |
122 | );
123 |
124 | return (
125 | <>
126 | {/* {createdCategorySuccessfully()} */}
127 | {errorCreatingTag()}
128 | {/* {removedCategorySuccessfully()} */}
129 |
130 |
131 | {newTagForm()}
132 | {loopingThroughTagLists()}
133 |
134 | >
135 | );
136 | };
137 |
138 | export default Tag;
--------------------------------------------------------------------------------
/client/components/Header.js:
--------------------------------------------------------------------------------
1 | import React, { useState,useEffect } from 'react';
2 | import Link from "next/link";
3 | import {APP_NAME} from "../config.js";
4 | import NProgress from "nprogress";
5 | import {logout,isAuthenticated} from "../actions/authentication.js";
6 | import Router from "next/router";
7 | import {useLoaded} from "../hooks/useLoaded";
8 | import BookIcon from '@material-ui/icons/Book';
9 | import {
10 | Collapse,
11 | Navbar,
12 | NavbarToggler,
13 | NavbarBrand,
14 | Nav,
15 | NavItem,
16 | NavLink,
17 | UncontrolledDropdown,
18 | DropdownToggle,
19 | DropdownMenu,
20 | DropdownItem,
21 | NavbarText
22 | } from 'reactstrap';
23 | import ExitToAppIcon from '@material-ui/icons/ExitToApp';
24 | import DashboardIcon from '@material-ui/icons/Dashboard';
25 | import VpnKeyIcon from '@material-ui/icons/VpnKey';
26 | import PersonAddIcon from '@material-ui/icons/PersonAdd';
27 | import ComputerIcon from '@material-ui/icons/Computer';
28 | import Typical from 'react-typical';
29 |
30 |
31 | Router.onRouteChangeStart = url => NProgress.start();
32 | Router.onRouteChangeComplete = url => NProgress.done();
33 | Router.onRouteChangeError = url => NProgress.done();
34 |
35 | const Header = (props) => {
36 | const [isOpen, setIsOpen] = useState(false);
37 |
38 | const toggle = () => setIsOpen(!isOpen);
39 |
40 | const loaded = useLoaded();
41 |
42 | return (
43 | <>
44 |
45 | {/* */}
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | <>
62 |
63 |
64 |
65 |
66 | {` `}
67 | Blogs
68 |
69 |
70 | {` `}
71 |
72 | >
73 |
74 |
75 | {!isAuthenticated() && loaded && (
76 | <>
77 |
78 |
79 |
80 |
81 |
82 | {` `}
83 | Login
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 | Resgister
93 |
94 |
95 |
96 | >
97 | )}
98 |
99 |
100 |
101 |
102 | {isAuthenticated() && loaded && isAuthenticated().role === 0 &&(
103 | <>
104 |
105 |
106 |
107 |
108 | {`${isAuthenticated().name}'s Dashboard`}
109 |
110 |
111 |
112 | >
113 | )}
114 |
115 | {isAuthenticated() && loaded && isAuthenticated().role === 1 &&(
116 | <>
117 |
118 |
119 |
120 |
121 |
122 | {` ${isAuthenticated().name}'s Dashboard `}
123 |
124 |
125 |
126 | >
127 | )}
128 |
129 |
130 |
131 | {/* {JSON.stringify(isAuthenticated())} */}
132 | {isAuthenticated() && loaded && (
133 |
134 |
135 | logout(()=>Router.push(`/login`))}>
136 |
137 | Logout
138 |
139 |
140 |
141 | )}
142 |
143 |
144 |
145 |
146 |
147 | >
148 |
149 | );
150 | }
151 |
152 | export default Header;
--------------------------------------------------------------------------------
/client/pages/blogs/index.js:
--------------------------------------------------------------------------------
1 | import Head from 'next/head';
2 | import Link from 'next/link';
3 | import {withRouter} from "next/router";
4 | import Layout from '../../components/Layout';
5 | import React,{ useState,useEffect } from 'react';
6 | import { listBlogsWithCategoriesAndTaglists } from '../../actions/blog';
7 | import Card from "../../components/blog/Card";
8 | import LabelIcon from '@material-ui/icons/Label';
9 | import CategoryIcon from '@material-ui/icons/Category';
10 | import {API,DOMAIN,APP_NAME} from '../../config';
11 | import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
12 | import Search from "../../components/blog/Search";
13 |
14 |
15 | const Blogs = ({blogs,categories,taglists,totalBlogs,blogsLimit,blogSkip,router}) => {
16 | const head = ()=>{
17 |
18 | Tech Blogs | {APP_NAME}
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | {/* */}
30 |
31 | }
32 |
33 | const [skip,setSkip] = useState(0);
34 | const [limit,setLimit] = useState(blogsLimit);
35 | const [size,setSize] = useState(totalBlogs);
36 | const [loadedBlogs,setLoadedBlogs] = useState([]);
37 |
38 | const loadMoreBlogs = ()=>{
39 | let toSkip = skip + limit
40 | listBlogsWithCategoriesAndTaglists(toSkip, limit).then(data =>{
41 | if(data.error){
42 | console.log(data.error)
43 | } else {
44 | setLoadedBlogs([...loadedBlogs,...data.blogs])
45 | setSize(data.size)
46 | setSkip(toSkip)
47 | }
48 | })
49 | };
50 |
51 | const loadMoreBlogButton =()=>{
52 | return (
53 | size > 0 && size >= limit && (Load More )
54 | )
55 | };
56 |
57 | const showingAllLoadedBlogs = ()=>{
58 | return loadedBlogs.map((blog,index)=>{
59 | return (
60 |
61 |
62 |
63 |
64 | )
65 | })
66 | };
67 |
68 | const listAndDisplayAllBlogs = ()=>{
69 | return blogs.map((blog,index)=>(
70 |
71 |
72 |
73 |
74 | ))
75 | };
76 |
77 | const listAndDisplayAllTheCategories =() =>{
78 | return categories.map((cat,index)=>{
79 | return (
80 |
81 | {cat.name}
82 |
83 | )
84 | })
85 | };
86 |
87 | const listAndDisplayAllTheTaglists =() =>{
88 | return taglists.map((tagg,index)=>{
89 | return (
90 |
91 | {tagg.name}
92 |
93 | )
94 | })
95 | };
96 |
97 | return (
98 | <>
99 | {head()}
100 |
101 |
102 |
118 | {listAndDisplayAllBlogs()}
119 | {showingAllLoadedBlogs()}
120 | {loadMoreBlogButton()}
121 |
122 |
123 |
124 | >
125 | );
126 | };
127 |
128 | // lifecycle methods that comes with next js
129 | // getInitialProps
130 | // getInitialProps can only be used on the pages not on the components
131 |
132 | Blogs.getInitialProps =()=>{
133 | let skip = 0
134 | let limit = 3
135 | return listBlogsWithCategoriesAndTaglists(skip,limit).then(data =>{
136 | if(data.error){
137 | console.log(data.error)
138 | } else {
139 | return {
140 | blogs: data.blogs,
141 | categories: data.categories,
142 | taglists: data.taglists,
143 | totalBlogs: data.size,
144 | blogsLimit: limit,
145 | blogSkip: skip
146 | };
147 | }
148 | })
149 | };
150 |
151 | export default withRouter(Blogs);
--------------------------------------------------------------------------------
/client/pages/blogs/[slug].js:
--------------------------------------------------------------------------------
1 | import Head from 'next/head';
2 | import Link from 'next/link';
3 | import {withRouter} from "next/router";
4 | import Layout from '../../components/Layout';
5 | import React,{ useState,useEffect } from 'react';
6 | import { singleBlog,blogListRelated } from '../../actions/blog';
7 | import LabelIcon from '@material-ui/icons/Label';
8 | import CategoryIcon from '@material-ui/icons/Category';
9 | import {API,DOMAIN,APP_NAME} from '../../config';
10 | import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
11 | import moment from 'moment';
12 | import renderHTML from 'react-render-html';
13 | import SmallCard from "../../components/blog/SmallCard";
14 |
15 |
16 |
17 |
18 |
19 | const SingleBlog = ({blog,query})=>{
20 |
21 | const head = ()=>(
22 |
23 | {blog.title} | {APP_NAME}
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | {/* */}
35 |
36 | );
37 |
38 | const [showBlogRelated,setShowBlogRelated] = useState([]);
39 |
40 | const loadBlogRelated = ()=>{
41 | blogListRelated({blog}).then(data=>{
42 | if(data.error){
43 | console.log(data.error)
44 | }else{
45 | setShowBlogRelated(data);
46 | }
47 | })
48 | };
49 |
50 | const showingRelatedBlogs = ()=>{
51 | return showBlogRelated.map((blog,index)=>{
52 | return (
53 |
58 | )
59 | })
60 | }
61 |
62 | useEffect(()=>{
63 | loadBlogRelated();
64 | },[])
65 |
66 |
67 |
68 |
69 | const listAndDisplayAllCategories = blog => {
70 | return (
71 | blog.categories.map((cat,index)=>{
72 | return (
73 |
74 | {cat.name}
75 |
76 | )
77 | })
78 | )
79 | };
80 |
81 | const listAndDisplayAllTaglists = blog => {
82 | return (
83 | blog.taglists.map((tagg,index)=>{
84 | return (
85 |
86 | {tagg.name}
87 |
88 | )
89 | })
90 | )
91 | };
92 |
93 | return (
94 | <>
95 | {head()}
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
Author : {blog.postedBy.name} | Published {moment(blog.updatedAt).fromNow()}
108 |
109 |
110 | {listAndDisplayAllCategories(blog)}{listAndDisplayAllTaglists(blog)}
111 |
112 |
113 |
114 |
115 |
{blog.title}
116 |
117 |
118 |
119 |
120 |
121 | {renderHTML(blog.body)}
122 |
123 |
124 |
125 | {/* // Related blogs */}
126 |
127 |
Related Blogs
128 |
129 | {/*
Show Related Blogs
*/}
130 | {/* {JSON.stringify(showBlogRelated)} */}
131 |
132 | {showingRelatedBlogs()}
133 |
134 |
135 |
136 | {/* Disqus commneting */}
137 |
138 |
139 |
Disqus Comments
140 |
141 |
142 |
143 |
144 | >
145 | )
146 | }
147 |
148 | SingleBlog.getInitialProps=({query})=>{
149 | return singleBlog(query.slug).then(data=>{
150 | if (data.error){
151 | console.log(data.error)
152 | } else {
153 | return {blog:data,query}
154 | }
155 | })
156 | }
157 |
158 | export default withRouter(SingleBlog);
--------------------------------------------------------------------------------
/client/pages/index.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import LandingLayout from "../components/LandingPage/Layout";
3 | import Layout from "../components/Layout";
4 | import Link from "next/link";
5 | import Swal from "sweetalert2";
6 | import DoubleArrowIcon from "@material-ui/icons/DoubleArrow";
7 | import ArrowDownwardIcon from "@material-ui/icons/ArrowDownward";
8 | import BookIcon from "@material-ui/icons/Book";
9 |
10 | const Index = () => {
11 | return (
12 | <>
13 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | Tech Blogsite
24 |
25 |
26 |
27 | "Tech Blogsite"
28 | Browse as well as create blogs related to various different
29 | kind technologies.
30 |
31 |
32 |
Take your blogging skills to next level
33 |
34 |
35 |
36 | Get Started
37 |
38 |
39 |
40 |
41 | {/*
42 | Tech Blogs
43 | */}
44 |
45 | Blogs
46 |
47 |
48 |
49 |
50 |
51 |
56 |
57 |
Tech Blogsite
58 |
About this blog
59 |
60 |
61 |
62 | Welcome to the "tech blogsite" a very easy and elegant way to
63 | create and to browse a blog which is built entirely on MERN
64 | stack. The frontend of this site is created using nextjs and
65 | reactjs. Express and node is running on the backend and MongoDb
66 | is used as an Database for storing blogs as well as user
67 | profiles. Join now and create your very own blog without any
68 | hassle.
69 |
70 |
71 |
72 |
75 |
76 |
77 |
78 |
83 |
84 |
89 |
94 |
95 |
Tech Blogsite
96 |
97 | Premium blog content with absolutely no subscribtion fees.
98 |
99 |
100 |
101 |
102 | Each of blogs are curated from a detailed study, so it is
103 | attractive as well as informative. As always, following the
104 | current technical requirements and standards of the industry.The
105 | main purpose of this blogsite is to impact the whole world by
106 | providing them free technical skills, information and knowledge
107 | about technologies completely for free.
108 |
109 |
110 |
111 |
112 |
117 |
118 |
Tech Blogsite
119 |
Easy and Perfect User Interface
120 |
121 |
122 |
123 | Polished and refined blogs.It's hard to look away when faced
124 | with a minimalist yet striking design such as this blogsite. The
125 | dark and smoky black background coupled with a striking glass
126 | transparent effect make this a winning color scheme, which makes
127 | this site very eye pleasing.
128 |
129 |
130 |
131 |
132 | Blogs
133 |
134 |
135 |
136 |
137 |
142 |
143 |
144 |
149 |
150 |
151 |
152 | “Those who are happiest are those who do the most for others.”
153 |
154 |
155 |
156 |
— Booker T. Washington
157 |
158 |
159 |
170 |
171 |
172 | >
173 | );
174 | };
175 |
176 | export default Index;
177 |
--------------------------------------------------------------------------------
/client/components/update/NewBlog.js:
--------------------------------------------------------------------------------
1 | import Link from "next/link";
2 | import React,{useState,useEffect} from "react";
3 | import Router from "next/router";
4 | import dynamic from "next/dynamic"; // react quill runs on client side so dynamic is used to turn off ssr for react quill(to dynamically load the component)
5 | import {withRouter} from "next/router"; // to get access to the router props from the components
6 | // importing actions
7 | import {getCookie,isAuthenticated} from "../../actions/authentication";
8 | import {getCategories} from "../../actions/category";
9 | import {getTagLists} from "../../actions/tag";
10 | import {createBlog} from "../../actions/blog";
11 | import PublishIcon from '@material-ui/icons/Publish';
12 | import {ReactQuillModules,ReactQuillFormats} from "../../helpers/ReactQuill";
13 |
14 |
15 | // dynamically importing react quill
16 | const ReactQuill = dynamic(() => import("react-quill"),{ssr:false});
17 |
18 |
19 | const NewBlog = ({ router }) => {
20 | const blogFromLS = () => {
21 | if (typeof window === 'undefined') {
22 | return false;
23 | }
24 |
25 | if (localStorage.getItem('blog')) {
26 | return JSON.parse(localStorage.getItem('blog'));
27 | } else {
28 | return false;
29 | }
30 | };
31 |
32 | const [categories, setCategories] = useState([]);
33 | const [taglists, setTaglists] = useState([]);
34 |
35 | const [checked, setChecked] = useState([]); // categories
36 | const [checkedTag, setCheckedTag] = useState([]); // taglists
37 |
38 | const [body, setBody] = useState(blogFromLS());
39 | const [infos, setInfos] = useState({
40 | error: '',
41 | sizeError: '',
42 | success: '',
43 | formData: '',
44 | title: '',
45 | hidePublishButton: false
46 | });
47 |
48 | const { error, sizeError, success, formData, title, hidePublishButton } = infos;
49 | const token = getCookie("token");
50 |
51 | useEffect(() => {
52 | setInfos({ ...infos, formData: new FormData() });
53 | initializeCategories();
54 | initializeTaglists();
55 | }, [router]);
56 |
57 | const initializeCategories = () => {
58 | getCategories().then(data => {
59 | if (data.error) {
60 | setInfos({ ...infos, error: data.error });
61 | } else {
62 | setCategories(data);
63 | }
64 | });
65 | };
66 |
67 | const initializeTaglists = () => {
68 | getTagLists().then(data => {
69 | if (data.error) {
70 | setInfos({ ...infos, error: data.error });
71 | } else {
72 | setTaglists(data);
73 | }
74 | });
75 | };
76 |
77 | const publishBlog = event => {
78 | event.preventDefault();
79 | // console.log('ready to publishBlog');
80 | createBlog(formData,token).then(data=>{
81 | if(data.error){
82 | setInfos({...infos,error:data.error})
83 | } else {
84 | setInfos({...infos,title:"",error:"",success:`"${data.title}" is successfully published`})
85 | setBody("");
86 | setCategories([]);
87 | setTaglists([]);
88 | }
89 | })
90 | };
91 |
92 | if (publishBlog.error){
93 | console.log(error)
94 | }
95 |
96 | const handleChange = name => event => {
97 | // console.log(e.target.value);
98 | const value = name === 'photo' ? event.target.files[0] : event.target.value;
99 | formData.set(name, value);
100 | setInfos({ ...infos, [name]: value, formData, error: '' });
101 | };
102 |
103 | const handleBody = event => {
104 | // console.log(e);
105 | setBody(event);
106 | formData.set('body', event);
107 | if (typeof window !== 'undefined') {
108 | localStorage.setItem('blog', JSON.stringify(event));
109 | }
110 | };
111 |
112 | const handleCategoriesToggle = cat => () => {
113 | setInfos({ ...infos, error: '' });
114 | // return the first index or -1
115 | const clickedCategory = checked.indexOf(cat);
116 | const all = [...checked];
117 |
118 | if (clickedCategory === -1) {
119 | all.push(cat);
120 | } else {
121 | all.splice(clickedCategory, 1);
122 | }
123 | console.log(all);
124 | setChecked(all);
125 | formData.set('categories', all);
126 | };
127 |
128 | const handleTaglistsToggle = tagg => () => {
129 | setInfos({ ...infos, error: '' });
130 | // return the first index or -1
131 | const clickedTags = checkedTag.indexOf(tagg);
132 | const all = [...checkedTag];
133 |
134 | if (clickedTags === -1) {
135 | all.push(tagg);
136 | } else {
137 | all.splice(clickedTags, 1);
138 | }
139 | console.log(all);
140 | setCheckedTag(all);
141 | formData.set('taglists', all);
142 | };
143 |
144 | const displayCategories = () => {
145 | return (
146 | categories &&
147 | categories.map((cat, index) => (
148 |
149 |
150 | {cat.name}
151 |
152 | ))
153 | );
154 | };
155 |
156 | const displayTaglists = () => {
157 | return (
158 | taglists &&
159 | taglists.map((tagg, index) => (
160 |
161 |
162 | {tagg.name}
163 |
164 | ))
165 | );
166 | };
167 |
168 | const displayError=()=>{
169 | return (
170 | {error}
171 | )
172 | };
173 | const displaySuccess=()=>{
174 | return (
175 | {success}
176 | )
177 | };
178 |
179 |
180 | const createBlogForm = () => {
181 | return (
182 |
206 | );
207 | };
208 |
209 | return (
210 |
211 |
212 |
213 |
214 |
215 |
Featured Background Image
216 |
217 |
Maximum file size : 1024kb
218 |
Upload Image
219 |
220 | {/* */}
221 |
222 |
223 |
224 |
225 |
Select Categories
226 |
227 |
228 |
229 |
230 |
231 |
Select Tags
232 |
233 |
234 |
235 |
236 |
237 |
238 | {displayError()}
239 | {displaySuccess()}
240 | {createBlogForm()}
241 |
242 | {/*
243 | {JSON.stringify(title)}
244 |
245 | {JSON.stringify(body)}
246 |
247 | {JSON.stringify(categories)}
248 |
249 | {JSON.stringify(taglists)} */}
250 |
251 |
252 |
253 | );
254 | };
255 |
256 |
257 | export default withRouter(NewBlog);
--------------------------------------------------------------------------------
/client/components/update/UpdateNewBlog.js:
--------------------------------------------------------------------------------
1 | import Link from "next/link";
2 | import React, { useState, useEffect } from "react";
3 | import Router from "next/router";
4 | import dynamic from "next/dynamic"; // react quill runs on client side so dynamic is used to turn off ssr for react quill(to dynamically load the component)
5 | import { withRouter } from "next/router"; // to get access to the router props from the components
6 | // importing actions
7 | import { getCookie, isAuthenticated } from "../../actions/authentication";
8 | import { getCategories } from "../../actions/category";
9 | import { getTagLists } from "../../actions/tag";
10 | import { singleBlog, updatingTheBlog } from "../../actions/blog";
11 | import PublishIcon from "@material-ui/icons/Publish";
12 | import { ReactQuillModules, ReactQuillFormats } from "../../helpers/ReactQuill";
13 | import { API } from "../../config";
14 |
15 | // dynamically importing react quill
16 | const ReactQuill = dynamic(() => import("react-quill"), { ssr: false });
17 |
18 | const UpdateNewBlog = ({ router }) => {
19 | const [body, setBody] = useState("");
20 | const [categories, setCategories] = useState([]);
21 | const [taglists, setTaglists] = useState([]);
22 |
23 | const [checked, setChecked] = useState([]); // categories
24 | const [checkedTag, setCheckedTag] = useState([]); // taglists
25 |
26 | const [infos, setInfos] = useState({
27 | title: "",
28 | error: "",
29 | success: "",
30 | formData: "",
31 | body: "",
32 | });
33 |
34 | const { title, error, success, formData } = infos;
35 | const token = getCookie("token");
36 |
37 | useEffect(() => {
38 | setInfos({ ...infos, formData: new FormData() });
39 | initializeBlog();
40 | initializeCategories();
41 | initializeTaglists();
42 | }, [router]);
43 |
44 | const handleBody = (event) => {
45 | setBody(event);
46 | formData.set("body", event);
47 | };
48 |
49 | const editingTheBlog = (event) => {
50 | event.preventDefault();
51 | updatingTheBlog(formData, token, router.query.slug).then((data) => {
52 | if (data.error) {
53 | setInfos({ ...infos, error: data.error });
54 | } else {
55 | setInfos({
56 | ...infos,
57 | title: "",
58 | success: `${data.title} is successfully edited `,
59 | });
60 | if (isAuthenticated() && isAuthenticated().role === 1) {
61 | Router.replace(`/adminDashboard/update/${router.query.slug}`);
62 | // Router.replace(`/adminDashboard`)
63 | } else if (isAuthenticated() && isAuthenticated().role === 0) {
64 | Router.replace(`/userDashboard/update/${router.query.slug}`);
65 | // Router.replace(`/userDashboard}`)
66 | }
67 | }
68 | });
69 | };
70 |
71 | const showError = () => (
72 |
76 | {error}
77 |
78 | );
79 |
80 | const showSuccess = () => (
81 |
85 | {success}
86 |
87 | );
88 |
89 | const initializeBlog = () => {
90 | if (router.query.slug) {
91 | singleBlog(router.query.slug).then((data) => {
92 | if (data.error) {
93 | console.log(data.error);
94 | } else {
95 | setInfos({ ...infos, title: data.title });
96 | setBody(data.body);
97 | setCategoriesArray(data.categories);
98 | setTaglistsArray(data.taglists);
99 | }
100 | });
101 | }
102 | };
103 |
104 | const setCategoriesArray = (blogCategories) => {
105 | let catArray = [];
106 | blogCategories.map((cat, index) => {
107 | catArray.push(cat._id);
108 | });
109 | setChecked(catArray);
110 | };
111 |
112 | const setTaglistsArray = (blogTaglists) => {
113 | let tagArray = [];
114 | blogTaglists.map((tagg, index) => {
115 | tagArray.push(tagg._id);
116 | });
117 | setCheckedTag(tagArray);
118 | };
119 |
120 | const handleChange = (name) => (event) => {
121 | // console.log(e.target.value);
122 | const value = name === "photo" ? event.target.files[0] : event.target.value;
123 | formData.set(name, value);
124 | setInfos({ ...infos, [name]: value, formData, error: "" });
125 | };
126 |
127 | const initializeCategories = () => {
128 | getCategories().then((data) => {
129 | if (data.error) {
130 | setInfos({ ...infos, error: data.error });
131 | } else {
132 | setCategories(data);
133 | }
134 | });
135 | };
136 |
137 | const initializeTaglists = () => {
138 | getTagLists().then((data) => {
139 | if (data.error) {
140 | setInfos({ ...infos, error: data.error });
141 | } else {
142 | setTaglists(data);
143 | }
144 | });
145 | };
146 |
147 | const handleCategoriesToggle = (cat) => () => {
148 | setInfos({ ...infos, error: "" });
149 | // return the first index or -1
150 | const clickedCategory = checked.indexOf(cat);
151 | const all = [...checked];
152 |
153 | if (clickedCategory === -1) {
154 | all.push(cat);
155 | } else {
156 | all.splice(clickedCategory, 1);
157 | }
158 | console.log(all);
159 | setChecked(all);
160 | formData.set("categories", all);
161 | };
162 |
163 | const handleTaglistsToggle = (tagg) => () => {
164 | setInfos({ ...infos, error: "" });
165 | // return the first index or -1
166 | const clickedTags = checkedTag.indexOf(tagg);
167 | const all = [...checkedTag];
168 |
169 | if (clickedTags === -1) {
170 | all.push(tagg);
171 | } else {
172 | all.splice(clickedTags, 1);
173 | }
174 | console.log(all);
175 | setCheckedTag(all);
176 | formData.set("taglists", all);
177 | };
178 |
179 | const searchSkimCategory = (cat) => {
180 | const result = checked.indexOf(cat); // it will return true or -1
181 | if (result !== -1) {
182 | return true;
183 | } else {
184 | return false;
185 | }
186 | };
187 |
188 | const searchSkimTag = (tagg) => {
189 | const result = checkedTag.indexOf(tagg); // it will return true or -1
190 | if (result !== -1) {
191 | return true;
192 | } else {
193 | return false;
194 | }
195 | };
196 |
197 | const displayCategories = () => {
198 | return (
199 | categories &&
200 | categories.map((cat, index) => (
201 |
202 |
208 | {cat.name}
209 |
210 | ))
211 | );
212 | };
213 |
214 | const displayTaglists = () => {
215 | return (
216 | taglists &&
217 | taglists.map((tagg, index) => (
218 |
219 |
225 | {tagg.name}
226 |
227 | ))
228 | );
229 | };
230 |
231 | const editingTheBlogForm = () => {
232 | return (
233 |
262 | );
263 | };
264 |
265 | return (
266 |
267 |
268 |
269 |
270 |
271 |
Featured Background Image
272 |
273 | {body && (
274 |
279 | )}
280 |
281 |
Maximum file size : 1024kb
282 |
283 | Upload Image
284 |
290 | {/* */}
291 |
292 |
293 |
294 |
295 |
Select Categories
296 |
297 |
298 | {displayCategories()}
299 |
300 |
301 |
302 |
303 |
Select Tags
304 |
305 |
306 | {displayTaglists()}
307 |
308 |
309 |
310 |
311 | {showSuccess()}
312 | {showError()}
313 | {editingTheBlogForm()}
314 | {/* {displayError()}
315 | {displaySuccess()}
316 | {createBlogForm()} */}
317 | {/*
318 | {JSON.stringify(title)}
319 |
320 | {JSON.stringify(body)}
321 |
322 | {JSON.stringify(categories)}
323 |
324 | {JSON.stringify(taglists)} */}
325 |
326 |
327 |
328 | );
329 | };
330 | export default withRouter(UpdateNewBlog);
331 |
--------------------------------------------------------------------------------
/client/static/assets/coding.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
36 | 111-coding
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
54 |
56 |
57 |
58 |
59 |
61 |
63 |
64 |
65 |
67 |
69 |
71 |
73 |
75 |
76 |
77 |
78 |
79 |
81 |
82 |
84 |
86 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
108 |
109 |
110 |
112 |
114 |
116 |
118 |
120 |
121 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
148 |
154 |
156 |
157 |
158 |
159 |
--------------------------------------------------------------------------------
/client/static/assets/svg/developer.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/server/controllers/blog.js:
--------------------------------------------------------------------------------
1 | // for creating forms
2 | const formidable = require("formidable");
3 | // to create slugs
4 | const slugify = require("slugify");
5 | // stripping the html from the body to create excerpt
6 | // const stripHtml = require("string-strip-html");
7 | const stripHtml = require("cli-strip-html");
8 | // lodash for updating the blogs
9 | const _ = require("lodash");
10 | // models
11 | const Blog = require("../models/blogSchema");
12 | const Category = require("../models/categorySchema");
13 | const Tag = require("../models/tagSchema");
14 | const User = require("../models/user");
15 | // Handling errors (sending mongoose error to the client)
16 | const {errorHandler} = require("../helpers/databaseErrorHandler");
17 | // node js file system
18 | const fs = require("fs");
19 | // excerpt trim
20 | const {excerptTrim} = require("../helpers/excerptTrim");
21 | require("dotenv").config();
22 |
23 | exports.create = (req, res) => {
24 | let form = new formidable.IncomingForm();
25 | form.keepExtensions = true;
26 | form.parse(req, (err, fields, files) => {
27 | if (err) {
28 | return res.status(400).json({
29 | error: 'Image upload failure'
30 | });
31 | }
32 |
33 |
34 | const { title, body, categories, taglists } = fields;
35 |
36 |
37 | if (!title || !title.length) {
38 | return res.status(400).json({
39 | error: `--------------------->Title is required<----------------------`
40 | });
41 | }
42 |
43 | if (!body || body.length < 200) {
44 | return res.status(400).json({
45 | error: `-------------------->Blog is either short or too big<----------------------`
46 | });
47 | }
48 |
49 | if (!categories || categories.length === 0) {
50 | return res.status(400).json({
51 | error: '------------------->Please select at least one category<-----------------'
52 | });
53 | }
54 |
55 | if (!taglists || taglists.length === 0) {
56 | return res.status(400).json({
57 | error: '---------------------->Please select at least one tag<--------------------'
58 | });
59 | }
60 |
61 | let blog = new Blog();
62 | blog.title = title;
63 | blog.body = body;
64 | // console.log("rerererr",body)
65 | blog.excerpt = excerptTrim(body, 160, ' ', '.....');
66 | blog.slug = slugify(title).toLowerCase();
67 | blog.mtitle = `${title} | ${process.env.BLOG_NAME}`;
68 | blog.mdesc = stripHtml(body.substring(0,160));
69 | blog.postedBy = req.user._id;
70 | // categories and tags
71 | let allTheListOfCategories = categories && categories.split(',');
72 | let allTheListOfTags = taglists && taglists.split(',');
73 | if (files.photo) {
74 | if (files.photo.size > 10000000) {
75 | return res.status(400).json({
76 | error: 'Image size too big'
77 | });
78 | }
79 |
80 | blog.photo.data = fs.readFileSync(files.photo.path);
81 | blog.photo.contentType = files.photo.type;
82 | }
83 | blog.save((err, result) => {
84 | if (err) {
85 | // console.log("error_is_here",err)
86 |
87 | return res.status(400).json({
88 | // error: "wrong bitch"
89 | error: errorHandler(err)
90 | });
91 | }
92 | // res.json(result);
93 | Blog.findByIdAndUpdate(result._id, { $push: { categories: allTheListOfCategories } }, { new: true }).exec(
94 | (err, result) => {
95 | if (err) {
96 | return res.status(400).json({
97 | error: errorHandler(err)
98 | });
99 | } else {
100 | Blog.findByIdAndUpdate(result._id, { $push: { taglists: allTheListOfTags } }, { new: true }).exec(
101 | (err, result) => {
102 | if (err) {
103 | return res.status(400).json({
104 | error: errorHandler(err)
105 | });
106 | } else {
107 | res.json(result);
108 | }
109 | }
110 | );
111 | }
112 | }
113 | )
114 | });
115 | });
116 | };
117 |
118 | //list,bloglistsallCategoriesTags,read,remove,update
119 |
120 | exports.list =(req,res)=>{
121 | Blog.find({}).populate("categories","_id name slug").populate("taglists","_id name slug").populate("postedBy","_id name username") // second arguments is for particularly pupulating that specific field
122 | .select("_id title slug excerpt categories taglists postedBy createdAt updatedAt").exec((err,data)=>{
123 | if (err){
124 | return res.json({
125 | error: errorHandler(err)
126 | })
127 | }
128 | res.json(data)
129 | })
130 | }
131 |
132 | exports.bloglistsallCategoriesTags =(req,res)=>{
133 | let limit = req.body.limit ? parseInt(req.body.limit) : 10
134 | let skip = req.body.skip ? parseInt(req.body.skip) : 0
135 | let blogs
136 | let categories
137 | let tags
138 | Blog.find({}).populate("categories","_id name slug").populate("taglists","_id name slug")
139 | .populate("postedBy","_id name username profile").sort({createdAt: -1}).skip(skip).limit(limit) // second arguments is for particularly pupulating that specific field
140 | .select("_id title slug excerpt categories taglists postedBy createdAt updatedAt")
141 | .exec((err,data)=>{
142 | if (err){
143 | return res.json({
144 | error: errorHandler(err)
145 | })
146 | }
147 | blogs = data // we get all the blogs
148 | // getting all the categories
149 | Category.find({}).exec((err,cat)=>{
150 | if (err){
151 | return res.json({
152 | error: errorHandler(err)
153 | })
154 | }
155 | categories = cat // get all the categories
156 | // getting all the tags
157 | Tag.find({}).exec((err,tagg)=>{
158 | if (err){
159 | return res.json({
160 | error: errorHandler(err)
161 | })
162 | }
163 | taglists = tagg
164 | // return all the categories ,tags and the blogs
165 | res.json({blogs,categories,taglists,size: blogs.length});
166 | })
167 | })
168 | })
169 |
170 | }
171 |
172 | exports.read =(req,res)=>{
173 | const slug = req.params.slug.toLowerCase();
174 | Blog.findOne({slug}).populate("categories","_id name slug").populate("taglists","_id name slug").populate("postedBy","_id name username").select("_id title body slug mtitle mdesc categories taglists postedBy createdAt updatedAt").exec((err,data)=>{
175 | if (err){
176 | return res.json({
177 | error: errorHandler(err)
178 | })
179 | }
180 | res.json(data);
181 | })
182 | }
183 |
184 | exports.remove =(req,res)=>{
185 | const slug = req.params.slug.toLowerCase();
186 | Blog.findOneAndRemove({slug}).exec((err,data)=>{
187 | if(err){
188 | return res.json({
189 | error: errorHandler(err)
190 | })
191 | }
192 | res.json({
193 | message: "Blog has been succesfully deleted"
194 | })
195 | })
196 | };
197 |
198 |
199 | exports.update = (req, res) => {
200 | const slug = req.params.slug.toLowerCase();
201 |
202 | Blog.findOne({ slug }).exec((err, oldBlog) => {
203 | if (err) {
204 | return res.status(400).json({
205 | error: errorHandler(err)
206 | });
207 | }
208 |
209 | let form = new formidable.IncomingForm();
210 | form.keepExtensions = true;
211 |
212 | form.parse(req, (err, fields, files) => {
213 | if (err) {
214 | return res.status(400).json({
215 | error: 'Image could not upload'
216 | });
217 | }
218 |
219 | let slugBeforeMerge = oldBlog.slug;
220 | oldBlog = _.merge(oldBlog, fields);
221 | oldBlog.slug = slugBeforeMerge;
222 |
223 | const { body, mdesc, categories, taglists } = fields;
224 |
225 | if (body) {
226 | oldBlog.excerpt = excerptTrim(body, 320, ' ', ' ...');
227 | oldBlog.mdesc = stripHtml(body.substring(0, 160));
228 | }
229 |
230 | if (categories) {
231 | oldBlog.categories = categories.split(',');
232 | }
233 |
234 | if (taglists) {
235 | oldBlog.taglists = taglists.split(',');
236 | }
237 |
238 | if (files.photo) {
239 | if (files.photo.size > 10000000) {
240 | return res.status(400).json({
241 | error: 'Image should be less then 1mb in size'
242 | });
243 | }
244 | oldBlog.photo.data = fs.readFileSync(files.photo.path);
245 | oldBlog.photo.contentType = files.photo.type;
246 | }
247 |
248 | oldBlog.save((err, result) => {
249 | if (err) {
250 | return res.status(400).json({
251 | error: errorHandler(err)
252 | });
253 | }
254 | // result.photo = undefined;
255 | res.json(result);
256 | });
257 | });
258 | });
259 | };
260 |
261 | exports.photo =(req,res)=>{
262 | const slug = req.params.slug.toLowerCase();
263 | Blog.findOne({slug}).select("photo").exec((err,blog)=>{
264 | if (err || !blog) {
265 | return res.status(400).json({
266 | error: errorHandler(err)
267 | })
268 | }
269 | res.set('Content-Type',blog.photo.contentType)
270 | return res.send(blog.photo.data);
271 | })
272 | };
273 |
274 | exports.blogListRelated=(req,res)=>{
275 | let limit = req.body.limit ? parseInt(req.body.limit) : 3
276 |
277 | const {_id,categories} = req.body.blog
278 |
279 | // not including _id but including categories
280 | // while showing related blogs excluding the blog itself and showing other blogs instead
281 | Blog.find({_id: {$ne: _id},categories: {$in: categories}}).limit(limit).populate('postedBy','_id name profile').select('slug title excerpt postedBy createdAt updatedAt')
282 | .exec((err,blogs)=>{
283 | if (err){
284 | return res.status(400).json({
285 | error: "Blog not found"
286 | })
287 | }
288 | res.json(blogs);
289 | })
290 | };
291 |
292 | exports.listSearchItems=(req,res)=>{
293 | const {search}= req.query
294 | if(search){
295 | Blog.find({
296 | $or: [{title:{$regex: search,$options: 'i' }},{body:{$regex: search,$options:"i"}}]
297 | },(err,blogs)=>{
298 | if(err){
299 | return res.status(400).json({
300 | error:errorHandler(err)
301 | })
302 | }
303 | res.json(blogs)
304 | }).select("-photo -body");
305 | }
306 | };
307 |
308 |
--------------------------------------------------------------------------------
/client/static/assets/Lantern.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
52 |
56 |
60 |
62 |
64 |
66 |
70 |
72 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
84 |
85 |
86 |
88 |
89 |
90 |
91 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
111 |
113 |
114 |
116 |
118 |
120 |
122 |
123 |
124 |
126 |
127 |
129 |
131 |
132 |
133 |
134 |
136 |
138 |
140 |
141 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
152 |
154 |
155 |
156 |
157 |
158 |
161 |
163 |
166 |
169 |
170 |
--------------------------------------------------------------------------------
/client/static/assets/freelancing.svg:
--------------------------------------------------------------------------------
1 | freelancing
--------------------------------------------------------------------------------