├── frontend ├── src │ ├── App.css │ ├── components │ │ ├── ScrollToTop.jsx │ │ ├── AdminPrivateRoute.jsx │ │ ├── PrivateRoute.jsx │ │ ├── ThemeProvider.jsx │ │ ├── CallToAction.jsx │ │ ├── PostCard.jsx │ │ ├── OAuth.jsx │ │ ├── Footer.jsx │ │ ├── DashSidbar.jsx │ │ ├── Header.jsx │ │ ├── Comment.jsx │ │ ├── DashComments.jsx │ │ ├── DashUsers.jsx │ │ ├── DashPosts.jsx │ │ ├── CommentSection.jsx │ │ ├── DashboardComp.jsx │ │ └── DashProfile.jsx │ ├── redux │ │ ├── reducers │ │ │ ├── theme │ │ │ │ └── themeSlice.js │ │ │ └── user │ │ │ │ └── userSlice.js │ │ └── store │ │ │ └── store.js │ ├── pages │ │ ├── Projects.jsx │ │ ├── Dashboard.jsx │ │ ├── About.jsx │ │ ├── PageNotFound.jsx │ │ ├── Home.jsx │ │ ├── PostPage.jsx │ │ ├── SignIn.jsx │ │ ├── SignUp.jsx │ │ ├── CreatePost.jsx │ │ ├── Search.jsx │ │ └── UpdatePost.jsx │ ├── index.css │ ├── main.jsx │ ├── firebase.js │ └── App.jsx ├── .env ├── postcss.config.js ├── index.html ├── vite.config.js ├── .gitignore ├── README.md ├── tailwind.config.js ├── .eslintrc.cjs └── package.json ├── backend ├── utils │ ├── error.js │ └── verifyUser.js ├── router │ ├── auth.routes.js │ ├── post.routes.js │ ├── user.routes.js │ └── comment.routes.js ├── .gitignore ├── config │ └── config.js ├── model │ ├── comment.model.js │ ├── users.model.js │ └── post.model.js ├── package.json ├── index.js └── controller │ ├── auth.controller.js │ ├── post.controller.js │ ├── comment.controller.js │ └── user.controller.js └── .gitignore /frontend/src/App.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/.env: -------------------------------------------------------------------------------- 1 | VITE_FIREBASE_API = "AIzaSyDs7gM38dGMGUXMXr6Xou6NOZ3Xtyeq_p8" 2 | -------------------------------------------------------------------------------- /frontend/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /backend/utils/error.js: -------------------------------------------------------------------------------- 1 | const errorHandler = (statusCode, message) => { 2 | const error = new Error(); 3 | error.statusCode = statusCode; 4 | error.message = message; 5 | return error; 6 | }; 7 | 8 | module.exports = errorHandler; 9 | -------------------------------------------------------------------------------- /frontend/src/components/ScrollToTop.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import { useLocation } from "react-router-dom"; 3 | 4 | export default function ScrollToTop() { 5 | const { pathname } = useLocation(); 6 | useEffect(() => { 7 | window.scrollTo(0, 0); 8 | }, [pathname]); 9 | return null; 10 | } 11 | -------------------------------------------------------------------------------- /backend/router/auth.routes.js: -------------------------------------------------------------------------------- 1 | const { signup, signin, googleAuth } = require("../controller/auth.controller"); 2 | 3 | const authRoutes = require("express").Router(); 4 | 5 | authRoutes.post("/signup", signup); 6 | authRoutes.post("/signin", signin); 7 | authRoutes.post('/google', googleAuth); 8 | 9 | module.exports = authRoutes; 10 | -------------------------------------------------------------------------------- /frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Blog 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /frontend/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | server:{ 7 | proxy:{ 8 | '/api':{ 9 | target : 'http://localhost:8080', 10 | secure:false 11 | } 12 | } 13 | }, 14 | plugins: [react()], 15 | }) 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | .env -------------------------------------------------------------------------------- /frontend/src/components/AdminPrivateRoute.jsx: -------------------------------------------------------------------------------- 1 | import { useSelector } from "react-redux"; 2 | import { Navigate, Outlet } from "react-router-dom"; 3 | 4 | export function AdminPrivateRoute() { 5 | const { currentUser } = useSelector((state) => state.user); 6 | return currentUser && currentUser.isAdmin ? ( 7 | 8 | ) : ( 9 | 10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /backend/config/config.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | 4 | const startConnection=(URI)=>{ 5 | 6 | mongoose.connect(URI) 7 | .then((res)=>{ 8 | console.log(`Server is Connected to ${res.connection.host}`); 9 | }) 10 | .catch((err)=>{ 11 | console.log(`Error connecting to ${err.message}`); 12 | }); 13 | 14 | } 15 | 16 | module.exports = startConnection; -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # React + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | 7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh 8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 | -------------------------------------------------------------------------------- /frontend/src/redux/reducers/theme/themeSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | 3 | const initialState = { 4 | theme: "light", 5 | }; 6 | 7 | const themeSlice = createSlice({ 8 | name: "theme", 9 | initialState, 10 | reducers: { 11 | toogletheme: (state) => { 12 | state.theme = state.theme === "light" ? "dark" : "light"; 13 | }, 14 | }, 15 | }); 16 | 17 | export const { toogletheme } = themeSlice.actions; 18 | export default themeSlice.reducer; 19 | -------------------------------------------------------------------------------- /frontend/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | import Flowbite from 'flowbite/plugin' 3 | import tailwindScrollbar from "tailwind-scrollbar"; 4 | export default { 5 | content: [ 6 | "./index.html", 7 | "./src/**/*.{js,ts,jsx,tsx}", 8 | 'node_modules/flowbite-react/lib/esm/**/*.js', 9 | ], 10 | theme: { 11 | extend: {}, 12 | }, 13 | plugins: [ 14 | // require('flowbite/plugin'), 15 | Flowbite, 16 | tailwindScrollbar, 17 | ], 18 | } -------------------------------------------------------------------------------- /frontend/src/components/PrivateRoute.jsx: -------------------------------------------------------------------------------- 1 | import { useSelector } from "react-redux"; 2 | import { Navigate, Outlet } from "react-router-dom"; 3 | 4 | export function PrivateRoute() { 5 | const { currentUser } = useSelector((state) => state.user); 6 | return currentUser ? : ; 7 | } 8 | 9 | export function ProtectedRoute() { 10 | const { currentUser } = useSelector((state) => state.user); 11 | return currentUser ? : ; 12 | } 13 | -------------------------------------------------------------------------------- /backend/router/post.routes.js: -------------------------------------------------------------------------------- 1 | const { create, getPosts, updatePost, deletePost } = require('../controller/post.controller'); 2 | const verifyToken = require('../utils/verifyUser'); 3 | 4 | const postRoutes = require('express').Router(); 5 | 6 | 7 | postRoutes.post('/create', verifyToken, create); 8 | postRoutes.get('/getposts', getPosts); 9 | postRoutes.put('/updatepost/:postId/:userId', verifyToken, updatePost); 10 | postRoutes.delete('/deletepost/:postId/:userId', verifyToken, deletePost); 11 | 12 | module.exports = postRoutes; -------------------------------------------------------------------------------- /frontend/src/pages/Projects.jsx: -------------------------------------------------------------------------------- 1 | import CallToAction from "../components/CallToAction"; 2 | 3 | export default function Projects() { 4 | return ( 5 |
6 |

Pojects

7 |

8 | Build fun and engaging projects while learning HTML, CSS, and 9 | JavaScript! 10 |

11 | 12 |
13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /frontend/src/components/ThemeProvider.jsx: -------------------------------------------------------------------------------- 1 | import { useSelector } from "react-redux"; 2 | import PropTypes from "prop-types"; 3 | 4 | export default function ThemeProvider({ children }) { 5 | const { theme } = useSelector((state) => state.theme); 6 | return ( 7 |
8 |
9 | {children} 10 |
11 |
12 | ); 13 | } 14 | 15 | ThemeProvider.propTypes = { 16 | children: PropTypes.node, 17 | }; 18 | -------------------------------------------------------------------------------- /backend/router/user.routes.js: -------------------------------------------------------------------------------- 1 | const { test, updateUser, deleteUser, signOut, getUsers, getUser } = require("../controller/user.controller"); 2 | const verifyToken = require("../utils/verifyUser"); 3 | 4 | const userRoutes = require("express").Router(); 5 | 6 | userRoutes.get("/", test); 7 | userRoutes.put("/update/:userId", verifyToken, updateUser); 8 | userRoutes.delete("/delete/:userId", verifyToken, deleteUser); 9 | userRoutes.post("/signout", signOut); 10 | userRoutes.get("/getUsers", verifyToken, getUsers); 11 | userRoutes.get('/:userId', getUser); 12 | 13 | module.exports = userRoutes; 14 | -------------------------------------------------------------------------------- /frontend/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:react/recommended', 7 | 'plugin:react/jsx-runtime', 8 | 'plugin:react-hooks/recommended', 9 | ], 10 | ignorePatterns: ['dist', '.eslintrc.cjs'], 11 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, 12 | settings: { react: { version: '18.2' } }, 13 | plugins: ['react-refresh'], 14 | rules: { 15 | 'react/jsx-no-target-blank': 'off', 16 | 'react-refresh/only-export-components': [ 17 | 'warn', 18 | { allowConstantExport: true }, 19 | ], 20 | }, 21 | } 22 | -------------------------------------------------------------------------------- /backend/model/comment.model.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const commentSchema = mongoose.Schema( 4 | { 5 | content: { 6 | type: String, 7 | required: true, 8 | }, 9 | postId: { 10 | type: String, 11 | required: true, 12 | }, 13 | userId: { 14 | type: String, 15 | required: true, 16 | }, 17 | likes: { 18 | type: Array, 19 | default: [], 20 | }, 21 | numberofLikes: { 22 | type: Number, 23 | default: 0, 24 | }, 25 | }, 26 | { timestamps: true } 27 | ); 28 | 29 | const COMMENT = mongoose.model("COMMENT", commentSchema); 30 | 31 | module.exports = COMMENT; 32 | -------------------------------------------------------------------------------- /backend/utils/verifyUser.js: -------------------------------------------------------------------------------- 1 | const jwt = require('jsonwebtoken'); 2 | const errorHandler = require("./error"); 3 | 4 | 5 | const verifyToken = (req , res , next) => { 6 | const token = req.cookies.access_token 7 | if(!token){ 8 | return next(errorHandler(401,'Unauthorized access')); 9 | } 10 | try { 11 | jwt.verify(token,process.env.JWT_SECRETKEY, (err, user)=>{ 12 | if(err){ 13 | return next(errorHandler("Unauthorized Access")); 14 | } 15 | req.user = user; 16 | next() 17 | }) 18 | } catch (error) { 19 | next(error) 20 | } 21 | } 22 | 23 | module.exports = verifyToken -------------------------------------------------------------------------------- /frontend/src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | body { 6 | height: 100vh; 7 | } 8 | 9 | .ql-editor { 10 | font-size: 1.05rem; 11 | } 12 | .post-content p { 13 | margin-bottom: 0.5rem; 14 | } 15 | .post-content h1 { 16 | font-size: 1.5rem; 17 | font-weight: 600; 18 | font-family: sans-serif; 19 | margin: 1.5rem 0; 20 | } 21 | .post-content h2 { 22 | font-size: 1.4rem; 23 | font-family: sans-serif; 24 | margin: 1.5rem 0; 25 | } 26 | .post-content a { 27 | color: rgb(73, 149, 139); 28 | text-decoration: none; 29 | } 30 | .post-content a:hover { 31 | text-decoration: underline; 32 | } 33 | 34 | .dark .post-content a { 35 | color: red; 36 | } 37 | -------------------------------------------------------------------------------- /frontend/src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import App from "./App.jsx"; 4 | import "./index.css"; 5 | import { Provider } from "react-redux"; 6 | import { store, persistor } from "./redux/store/store.js"; 7 | import { PersistGate } from "redux-persist/integration/react"; 8 | import ThemeProvider from "./components/ThemeProvider.jsx"; 9 | 10 | ReactDOM.createRoot(document.getElementById("root")).render( 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | ); 21 | -------------------------------------------------------------------------------- /backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backend", 3 | "version": "1.0.0", 4 | "description": "Nitin's Blog Backend", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "node index.js", 9 | "dev": "nodemon start index.js", 10 | "build": "npm install && npm install --prefix ../frontend && npm run build --prefix ../frontend" 11 | }, 12 | "author": "Nitin Rajput", 13 | "license": "ISC", 14 | "dependencies": { 15 | "bcrypt": "^5.1.1", 16 | "cookie-parser": "^1.4.6", 17 | "cors": "^2.8.5", 18 | "dotenv": "^16.4.5", 19 | "express": "^4.18.3", 20 | "jsonwebtoken": "^9.0.2", 21 | "mongoose": "^8.2.2", 22 | "nodemon": "^3.1.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /backend/router/comment.routes.js: -------------------------------------------------------------------------------- 1 | const { 2 | createComment, 3 | getPostsComment, 4 | likeComment, 5 | editComment, 6 | deleteComment, 7 | getComments, 8 | } = require("../controller/comment.controller"); 9 | const verifyToken = require("../utils/verifyUser"); 10 | 11 | const commentRoutes = require("express").Router(); 12 | 13 | commentRoutes.post("/create", verifyToken, createComment); 14 | commentRoutes.get("/getPostComment/:postId", getPostsComment); 15 | commentRoutes.put("/likeComment/:commentId", verifyToken, likeComment); 16 | commentRoutes.put("/editComment/:commentId", verifyToken, editComment); 17 | commentRoutes.delete("/deleteComment/:commentId", verifyToken, deleteComment); 18 | commentRoutes.get('/getComments', verifyToken, getComments); 19 | 20 | module.exports = commentRoutes; 21 | -------------------------------------------------------------------------------- /frontend/src/firebase.js: -------------------------------------------------------------------------------- 1 | // Import the functions you need from the SDKs you need 2 | import { initializeApp } from "firebase/app"; 3 | // TODO: Add SDKs for Firebase products that you want to use 4 | // https://firebase.google.com/docs/web/setup#available-libraries 5 | 6 | // Your web app's Firebase configuration 7 | // For Firebase JS SDK v7.20.0 and later, measurementId is optional 8 | const firebaseConfig = { 9 | apiKey: import.meta.env.VITE_FIREBASE_API, 10 | authDomain: "nitin-s-blog.firebaseapp.com", 11 | projectId: "nitin-s-blog", 12 | storageBucket: "nitin-s-blog.appspot.com", 13 | messagingSenderId: "22846366881", 14 | appId: "1:22846366881:web:21063a883f4990a8617ce0", 15 | measurementId: "G-76GXJ4NHCS", 16 | }; 17 | 18 | // Initialize Firebase 19 | export const app = initializeApp(firebaseConfig); 20 | -------------------------------------------------------------------------------- /backend/model/users.model.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const userSchema = new mongoose.Schema( 4 | { 5 | username: { 6 | type: String, 7 | required: true, 8 | unique: true, 9 | }, 10 | email: { 11 | type: String, 12 | required: true, 13 | unique: true, 14 | }, 15 | password: { 16 | type: String, 17 | required: true, 18 | }, 19 | profilePicture: { 20 | type: String, 21 | default : 'https://w7.pngwing.com/pngs/340/946/png-transparent-avatar-user-computer-icons-software-developer-avatar-child-face-heroes-thumbnail.png', 22 | }, 23 | isAdmin: { 24 | type : Boolean, 25 | default : false, 26 | }, 27 | }, 28 | { timestamps: true } 29 | ); 30 | 31 | const USER = mongoose.model("USER", userSchema); 32 | 33 | module.exports = USER; 34 | -------------------------------------------------------------------------------- /backend/model/post.model.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const postSchema = new mongoose.Schema( 4 | { 5 | userId: { 6 | type: String, 7 | required: true, 8 | }, 9 | title: { 10 | type: String, 11 | required: true, 12 | }, 13 | content: { 14 | type: String, 15 | required: true, 16 | }, 17 | image: { 18 | type: String, 19 | default: 20 | "https://tiny-img.com/images/blog/thumb/blog-post-images-best-practices-in-2022.jpg", 21 | }, 22 | category: { 23 | type: String, 24 | default: "uncategorized", 25 | }, 26 | slug: { 27 | type: String, 28 | required: true, 29 | unique: true, 30 | }, 31 | }, 32 | { timestamps: true } 33 | ); 34 | 35 | const POST = mongoose.model("POST", postSchema); 36 | 37 | module.exports = POST; 38 | -------------------------------------------------------------------------------- /frontend/src/redux/store/store.js: -------------------------------------------------------------------------------- 1 | import { combineReducers, configureStore } from "@reduxjs/toolkit"; 2 | import userSlice from "../reducers/user/userSlice"; 3 | import themeSlice from "../reducers/theme/themeSlice"; 4 | import { persistReducer } from "redux-persist"; 5 | import storage from "redux-persist/lib/storage"; 6 | import persistStore from "redux-persist/es/persistStore"; 7 | 8 | // Root reducers all the reducers in the store 9 | const rootReducer = combineReducers({ 10 | user: userSlice, 11 | theme: themeSlice, 12 | }); 13 | 14 | const persistConfig = { 15 | key: "root", 16 | storage, 17 | version: 1, 18 | }; 19 | 20 | const persistedReducer = persistReducer(persistConfig, rootReducer); 21 | 22 | export const store = configureStore({ 23 | reducer: persistedReducer, 24 | middleware: (getDefaultMiddleware) => 25 | getDefaultMiddleware({ 26 | serializableCheck: false, 27 | }), 28 | }); 29 | 30 | export const persistor = persistStore(store); 31 | -------------------------------------------------------------------------------- /frontend/src/components/CallToAction.jsx: -------------------------------------------------------------------------------- 1 | import { Button } from "flowbite-react"; 2 | 3 | export default function CallToAction() { 4 | return ( 5 |
6 |
7 |

want to Learn more about JavaScript ?

8 |

9 | Check out these resources to learn more about JavaScript 10 |

11 | 23 |
24 |
25 | 29 |
30 |
31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@reduxjs/toolkit": "^2.2.2", 14 | "firebase": "^10.9.0", 15 | "flowbite-react": "^0.7.3", 16 | "framer-motion": "^11.3.7", 17 | "moment": "^2.30.1", 18 | "react": "^18.2.0", 19 | "react-circular-progressbar": "^2.1.0", 20 | "react-dom": "^18.2.0", 21 | "react-icons": "^5.0.1", 22 | "react-quill": "^2.0.0", 23 | "react-redux": "^9.1.0", 24 | "react-router-dom": "^6.22.2", 25 | "redux-persist": "^6.0.0" 26 | }, 27 | "devDependencies": { 28 | "@tailwindcss/line-clamp": "^0.4.4", 29 | "@types/react": "^18.2.56", 30 | "@types/react-dom": "^18.2.19", 31 | "@vitejs/plugin-react": "^4.2.1", 32 | "autoprefixer": "^10.4.18", 33 | "eslint": "^8.56.0", 34 | "eslint-plugin-react": "^7.33.2", 35 | "eslint-plugin-react-hooks": "^4.6.0", 36 | "eslint-plugin-react-refresh": "^0.4.5", 37 | "postcss": "^8.4.35", 38 | "tailwind-scrollbar": "^3.1.0", 39 | "tailwindcss": "^3.4.1", 40 | "vite": "^5.1.4" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /frontend/src/pages/Dashboard.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import { useLocation } from "react-router-dom"; 3 | import DashSidbar from "../components/DashSidbar"; 4 | import DashProfile from "../components/DashProfile"; 5 | import DashPosts from "../components/DashPosts"; 6 | import DashUsers from "../components/DashUsers"; 7 | import DashComments from "../components/DashComments"; 8 | import DashboardComp from "../components/DashboardComp"; 9 | 10 | export default function Dashboard() { 11 | const location = useLocation(); 12 | const [tab, setTab] = useState(""); 13 | useEffect(() => { 14 | const urlParms = new URLSearchParams(location.search); 15 | const tabFormUrl = urlParms.get("tab"); 16 | if (tabFormUrl) { 17 | setTab(tabFormUrl); 18 | } 19 | }, [location.search]); 20 | return ( 21 |
22 | {/* Dashboard SideBar */} 23 |
24 | 25 |
26 | {/* Profile Section */} 27 | {tab === "profile" && } 28 | {/* Admin Posts Section */} 29 | {tab === "posts" && } 30 | {/* All Users Sections */} 31 | {tab === "users" && } 32 | {/* All comments Section */} 33 | {tab === "comments" && } 34 | {/* Dashboard Compoent */} 35 | {tab === "dashboard" && } 36 |
37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /frontend/src/components/PostCard.jsx: -------------------------------------------------------------------------------- 1 | import { Link } from "react-router-dom"; 2 | import PropTypes from "prop-types"; 3 | 4 | export default function PostCard({ post }) { 5 | return ( 6 |
7 | 8 | post cover 13 | 14 |
15 |

{post.title}

16 | {post.category} 17 | 21 | Read article 22 | 23 |
24 |
25 | ); 26 | } 27 | 28 | PostCard.propTypes = { 29 | post: PropTypes.shape({ 30 | _id: PropTypes.string, 31 | title: PropTypes.string, 32 | image: PropTypes.string, 33 | category: PropTypes.string, 34 | createdAt: PropTypes.string, 35 | slug: PropTypes.string, 36 | }), 37 | }; 38 | -------------------------------------------------------------------------------- /frontend/src/pages/About.jsx: -------------------------------------------------------------------------------- 1 | export default function About() { 2 | return ( 3 |
4 |
5 |
6 |

7 | About Nitin's Blog 8 |

9 |
10 |

11 | Welcome to Nitin's Blog! This blog was created by Nitin 12 | Rajput as a personal project to share his thoughts and ideas with 13 | the world. Nitin is a passionate developer who loves to write 14 | about technology, coding, and everything in between. 15 |

16 | 17 |

18 | On this blog, you'll find weekly articles and tutorials on 19 | topics such as web development, software engineering, and 20 | programming languages. Nitin is always learning and exploring new 21 | technologies, so be sure to check back often for new content! 22 |

23 | 24 |

25 | We encourage you to leave comments on our posts and engage with 26 | other readers. You can like other people's comments and reply 27 | to them as well. We believe that a community of learners can help 28 | each other grow and improve. 29 |

30 |
31 |
32 |
33 |
34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /backend/index.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const connectDB = require("./config/config"); 3 | const userRoutes = require("./router/user.routes"); 4 | const authRoutes = require("./router/auth.routes"); 5 | const postRoutes = require("./router/post.routes"); 6 | const cookieParser = require("cookie-parser"); 7 | const cors = require("cors"); 8 | const commentRoutes = require("./router/comment.routes"); 9 | require("dotenv").config(); 10 | const path = require("path"); 11 | 12 | const PORT = process.env.PORT || 8080; 13 | const app = express(); 14 | app.use(express.json()); 15 | app.use(cookieParser()); 16 | app.use( 17 | cors({ 18 | origin: "*", 19 | }) 20 | ); 21 | 22 | const _dirname = path.resolve(); 23 | 24 | // database connection 25 | const startConnection = async () => { 26 | try { 27 | await connectDB(process.env.DB_URI); 28 | app.listen(PORT, () => { 29 | console.log(`Server is Runing on http://localhost:${PORT}`); 30 | }); 31 | } catch (err) { 32 | console.log(`Database is showing Error ${err.message}`); 33 | } 34 | }; 35 | 36 | startConnection(); 37 | 38 | app.use("/api/user", userRoutes); 39 | app.use("/api/auth", authRoutes); 40 | app.use("/api/post", postRoutes); 41 | app.use("/api/comment", commentRoutes); 42 | 43 | app.use(express.static(path.join(__dirname, "../frontend/dist"))); 44 | app.get("*", (req, res) => { 45 | res.sendFile(path.join(__dirname, "../frontend", "dist", "index.html")); 46 | }); 47 | 48 | //Middleware to handle the error 49 | app.use((err, req, res, next) => { 50 | const statusCode = err.statusCode || 500; 51 | const message = err.message || "Internal Server Error"; 52 | res.status(statusCode).json({ 53 | success: false, 54 | statusCode, 55 | message, 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /frontend/src/components/OAuth.jsx: -------------------------------------------------------------------------------- 1 | import { Button } from "flowbite-react"; 2 | import { AiFillGoogleCircle } from "react-icons/ai"; 3 | import { GoogleAuthProvider, getAuth, signInWithPopup } from "firebase/auth"; 4 | import { app } from "../firebase"; 5 | import { useDispatch } from "react-redux"; 6 | import { SignInSuccess } from "../redux/reducers/user/userSlice"; 7 | import { useNavigate } from "react-router-dom"; 8 | 9 | export default function OAuth() { 10 | const dispatch = useDispatch(); 11 | const navigate = useNavigate(); 12 | const auth = getAuth(app); 13 | const handleGoogleClick = async () => { 14 | const provider = new GoogleAuthProvider(); 15 | provider.setCustomParameters({ prompt: "select_account" }); 16 | try { 17 | const resultsFromGoogle = await signInWithPopup(auth, provider); 18 | // console.log(resultsFromGoogle) 19 | const res = await fetch("/api/auth/google", { 20 | method: "POST", 21 | headers: { "Content-Type": "application/json" }, 22 | body: JSON.stringify({ 23 | name: resultsFromGoogle.user.displayName, 24 | email: resultsFromGoogle.user.email, 25 | googlePhotoUrl: resultsFromGoogle.user.photoURL, 26 | }), 27 | }); 28 | const data = await res.json(); 29 | console.log("respone data", data); 30 | if (res.ok) { 31 | dispatch(SignInSuccess(data)); 32 | navigate("/"); 33 | } 34 | } catch (e) { 35 | console.log(e); 36 | } 37 | }; 38 | return ( 39 | 48 | ); 49 | } 50 | -------------------------------------------------------------------------------- /frontend/src/redux/reducers/user/userSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | 3 | const initialState = { 4 | currentUser: null, 5 | error: null, 6 | loading: false, 7 | }; 8 | 9 | const userSlice = createSlice({ 10 | name: "user", 11 | initialState, 12 | reducers: { 13 | SignInStart: (state) => { 14 | state.loading = true; 15 | state.error = null; 16 | }, 17 | SignInSuccess: (state, action) => { 18 | state.currentUser = action.payload; 19 | state.loading = false; 20 | state.error = null; 21 | }, 22 | SignInFailure: (state, action) => { 23 | state.loading = false; 24 | state.error = action.payload; 25 | }, 26 | UpdateStart: (state) => { 27 | state.loading = true; 28 | state.error = null; 29 | }, 30 | UpdateSucess: (state, action) => { 31 | state.currentUser = action.payload; 32 | state.loading = false; 33 | state.error = null; 34 | }, 35 | UpdateFailure: (state, action) => { 36 | state.loading = false; 37 | state.error = action.payload; 38 | }, 39 | deleteUserStart: (state) => { 40 | state.loading = true; 41 | state.error = null; 42 | }, 43 | deleteUserSucess: (state) => { 44 | state.currentUser = null; 45 | state.loading = false; 46 | state.error = null; 47 | }, 48 | deleteUserFailure: (state, action) => { 49 | state.loading = false; 50 | state.error = action.payload; 51 | }, 52 | SignOutSucess: (state) => { 53 | state.currentUser = null; 54 | state.error = null; 55 | state.loading = false; 56 | }, 57 | }, 58 | }); 59 | 60 | export const { 61 | SignInStart, 62 | SignInSuccess, 63 | SignInFailure, 64 | UpdateStart, 65 | UpdateSucess, 66 | UpdateFailure, 67 | deleteUserStart, 68 | deleteUserSucess, 69 | deleteUserFailure, 70 | SignOutSucess, 71 | } = userSlice.actions; 72 | 73 | export default userSlice.reducer; 74 | -------------------------------------------------------------------------------- /frontend/src/pages/PageNotFound.jsx: -------------------------------------------------------------------------------- 1 | // src/NotFound.js 2 | import { motion } from "framer-motion"; 3 | 4 | export const PageNotFound = () => { 5 | return ( 6 |
7 |
8 | 14 |
15 | 21 |

404

22 |

Oops! Page Not Found

23 | 28 | 35 | 41 | 42 | 43 | 47 | Go back to Home 48 | 49 |
50 |
51 | ); 52 | }; 53 | -------------------------------------------------------------------------------- /frontend/src/pages/Home.jsx: -------------------------------------------------------------------------------- 1 | import { Link } from "react-router-dom"; 2 | import CallToAction from "../components/CallToAction"; 3 | import { useEffect, useState } from "react"; 4 | import PostCard from "../components/PostCard"; 5 | 6 | export default function Home() { 7 | const [posts, setPosts] = useState([]); 8 | 9 | useEffect(() => { 10 | const fetchPosts = async () => { 11 | const res = await fetch("/api/post/getPosts?limit=12"); 12 | const data = await res.json(); 13 | setPosts(data.posts); 14 | }; 15 | fetchPosts(); 16 | }, []); 17 | return ( 18 |
19 |
20 |

Welcome to my Blog

21 |

22 | Here you'll find a variety of articles and tutorials on topics 23 | such as web development, software engineering, and programming 24 | languages. 25 |

26 | 30 | View all posts 31 | 32 |
33 |
34 | 35 |
36 | 37 |
38 | {posts && posts.length > 0 && ( 39 |
40 |

Recent Posts

41 |
42 | {posts.map((post) => ( 43 | 44 | ))} 45 |
46 | 50 | View all posts 51 | 52 |
53 | )} 54 |
55 |
56 | ); 57 | } 58 | -------------------------------------------------------------------------------- /frontend/src/App.jsx: -------------------------------------------------------------------------------- 1 | import { BrowserRouter, Route, Routes } from "react-router-dom"; 2 | import Home from "./pages/Home"; 3 | import About from "./pages/About"; 4 | import SignIn from "./pages/SignIn"; 5 | import SignUp from "./pages/SignUp"; 6 | import Dashboard from "./pages/Dashboard"; 7 | import Projects from "./pages/Projects"; 8 | import Header from "./components/Header"; 9 | import FooterComp from "./components/Footer"; 10 | import { PrivateRoute, ProtectedRoute } from "./components/PrivateRoute"; 11 | import { AdminPrivateRoute } from "./components/AdminPrivateRoute"; 12 | import CreatePost from "./pages/CreatePost"; 13 | import UpdatePost from "./pages/UpdatePost"; 14 | import PostPage from "./pages/PostPage"; 15 | import ScrollToTop from "./components/ScrollToTop"; 16 | import Search from "./pages/Search"; 17 | import { PageNotFound } from "./pages/PageNotFound"; 18 | 19 | export default function App() { 20 | return ( 21 | <> 22 | 23 | 24 |
25 |
26 | 27 | } /> 28 | } /> 29 | } /> 30 | {/* Admin Private Routes */} 31 | }> 32 | } /> 33 | } /> 34 | 35 | 36 | {/* For making protected route */} 37 | }> 38 | } /> 39 | } /> 40 | 41 | {/* For making protected route */} 42 | }> 43 | } /> 44 | 45 | 46 | } /> 47 | } /> 48 | } /> 49 | 50 | 51 | 52 | 53 | ); 54 | } 55 | -------------------------------------------------------------------------------- /frontend/src/components/Footer.jsx: -------------------------------------------------------------------------------- 1 | import { Footer } from "flowbite-react"; 2 | import { Link } from "react-router-dom"; 3 | import { BsFacebook, BsInstagram, BsLinkedin, BsGithub } from "react-icons/bs"; 4 | 5 | export default function FooterComp() { 6 | return ( 7 |
8 |
9 |
10 |
11 | 12 | 13 | Nitin's 14 | 15 | Blog 16 | 17 |
18 |
19 |
20 | 21 | 22 | 23 | 100 JS Project 24 | 25 | 26 | Nitin's Blogs 27 | 28 | 29 |
30 |
31 | 32 | 33 | 34 | Github 35 | 36 | Linkedin 37 | 38 |
39 |
40 | 41 | 42 | 43 | Privacy Policy 44 | 45 | Terms and Conditions 46 | 47 |
48 |
49 |
50 | 51 |
52 | 57 |
58 | 59 | 60 | 61 | 62 | 63 |
64 |
65 |
66 |
67 | ); 68 | } 69 | -------------------------------------------------------------------------------- /frontend/src/components/DashSidbar.jsx: -------------------------------------------------------------------------------- 1 | import { Sidebar } from "flowbite-react"; 2 | import { Link, useLocation } from "react-router-dom"; 3 | import { useEffect, useState } from "react"; 4 | import { 5 | HiAnnotation, 6 | HiArrowSmRight, 7 | HiDocumentText, 8 | HiOutlineUserGroup, 9 | HiUser, 10 | } from "react-icons/hi"; 11 | import { SignOutSucess } from "../redux/reducers/user/userSlice"; 12 | import { useDispatch, useSelector } from "react-redux"; 13 | 14 | export default function DashSidbar() { 15 | const location = useLocation(); 16 | const dispatch = useDispatch(); 17 | const { currentUser } = useSelector((state) => state.user); 18 | const [tab, setTab] = useState(""); 19 | useEffect(() => { 20 | const urlParms = new URLSearchParams(location.search); 21 | const tabFormUrl = urlParms.get("tab"); 22 | if (tabFormUrl) { 23 | setTab(tabFormUrl); 24 | } 25 | }, [location.search]); 26 | const handleSignOut = async () => { 27 | try { 28 | const res = await fetch("/api/user/signout", { 29 | method: "POST", 30 | }); 31 | const data = await res.json(); 32 | if (!res.ok) { 33 | console.log(data.message); 34 | } else { 35 | dispatch(SignOutSucess()); 36 | } 37 | } catch (error) { 38 | console.log(error.message); 39 | } 40 | }; 41 | return ( 42 | 43 | 44 | 45 | 46 | 53 | Profile 54 | 55 | 56 | {currentUser.isAdmin && ( 57 | <> 58 | 59 | 64 | Dashboard 65 | 66 | 67 | 68 | 73 | Post 74 | 75 | 76 | 77 | 82 | Comments 83 | 84 | 85 | 86 | 91 | Users 92 | 93 | 94 | 95 | )} 96 | 101 | Sign Out 102 | 103 | 104 | 105 | 106 | ); 107 | } 108 | -------------------------------------------------------------------------------- /frontend/src/pages/PostPage.jsx: -------------------------------------------------------------------------------- 1 | import { Button, Spinner } from "flowbite-react"; 2 | import { useEffect, useState } from "react"; 3 | import { Link, useParams } from "react-router-dom"; 4 | import CallToAction from "../components/CallToAction"; 5 | import CommentSection from "../components/CommentSection"; 6 | import PostCard from "../components/PostCard"; 7 | 8 | export default function PostPage() { 9 | const { postSlug } = useParams(); 10 | const [loading, setLoading] = useState(true); 11 | const [post, setPost] = useState(null); 12 | const [recentPosts, setRecentPosts] = useState(null); 13 | 14 | useEffect(() => { 15 | const fetchPost = async () => { 16 | try { 17 | setLoading(true); 18 | const res = await fetch(`/api/post/getposts?slug=${postSlug}`); 19 | const data = await res.json(); 20 | if (!res.ok) { 21 | setLoading(false); 22 | return; 23 | } 24 | if (res.ok) { 25 | setPost(data.posts[0]); 26 | setLoading(false); 27 | } 28 | } catch (error) { 29 | setLoading(false); 30 | } 31 | }; 32 | if (postSlug) { 33 | fetchPost(); 34 | } 35 | }, [postSlug]); 36 | 37 | useEffect(() => { 38 | try { 39 | const fetchRecentPosts = async () => { 40 | const res = await fetch(`/api/post/getposts?limit=3`); 41 | const data = await res.json(); 42 | if (res.ok) { 43 | setRecentPosts(data.posts); 44 | } 45 | }; 46 | fetchRecentPosts(); 47 | } catch (error) { 48 | console.log(error.message); 49 | } 50 | }, []); 51 | 52 | if (loading) { 53 | return ( 54 |
55 | 56 |
57 | ); 58 | } 59 | 60 | return ( 61 | <> 62 | {post && ( 63 |
64 |

65 | {post && post.title} 66 |

67 | 71 | 74 | 75 | {post 80 |
81 | {post && new Date(post.createdAt).toLocaleDateString()} 82 | 83 | {post && (post.content.length / 1000).toFixed(0)} mins read 84 | 85 |
86 |
90 |
91 | 92 |
93 | 94 |
95 | )} 96 |
97 |

Recent articles

98 |
99 | {recentPosts && 100 | recentPosts.map((post) => )} 101 |
102 |
103 | 104 | ); 105 | } 106 | -------------------------------------------------------------------------------- /backend/controller/auth.controller.js: -------------------------------------------------------------------------------- 1 | const bcrypt = require("bcrypt"); 2 | const jwt = require("jsonwebtoken"); 3 | const USER = require("../model/users.model"); 4 | const errorHandler = require("../utils/error"); 5 | 6 | const signup = async (req, res, next) => { 7 | const { username, email, password } = req.body; 8 | 9 | try { 10 | // Check if any required field is missing or empty 11 | if ( 12 | !username || 13 | !email || 14 | !password || 15 | username === "" || 16 | email === "" || 17 | password === "" 18 | ) { 19 | next(errorHandler(400, "All fields are required")); 20 | } 21 | 22 | // Check if the email is already registered 23 | const existingUser = await USER.findOne({ email }); 24 | if (existingUser) { 25 | next(errorHandler(400, "Email already registered")); 26 | } 27 | 28 | // Hash the password 29 | const hashedPassword = await bcrypt.hash(password, 10); 30 | 31 | // Create a new user 32 | const newUser = new USER({ 33 | username, 34 | email, 35 | password: hashedPassword, 36 | }); 37 | 38 | // Save the new user to the database 39 | await newUser.save(); 40 | 41 | res.json({ 42 | msg: "User Signup successful", 43 | }); 44 | } catch (error) { 45 | next(error); 46 | } 47 | }; 48 | 49 | const signin = async (req, res, next) => { 50 | const { email, password } = req.body; 51 | if (!email || !password || password === "" || email === "") { 52 | next(errorHandler(400, "All fields are required")); 53 | } 54 | try { 55 | const validUser = await USER.findOne({ email: email }); 56 | if (!validUser) { 57 | return next(errorHandler(400, "invaild email or password")); 58 | } 59 | const vaildPassword = bcrypt.compareSync(password, validUser.password); 60 | if (!vaildPassword) { 61 | return next(errorHandler(400, "invaild email or password")); 62 | } 63 | const token = jwt.sign({ id: validUser._id, isAdmin: validUser.isAdmin }, process.env.JWT_SECRETKEY, { 64 | expiresIn: "30day", 65 | }); 66 | 67 | // to remove the password from the response 68 | const { password: pass, ...rest } = validUser._doc; 69 | 70 | res 71 | .status(200) 72 | .cookie("access_token", token, { httpOnly: true }) 73 | .json(rest); 74 | } catch (err) { 75 | next(err); 76 | } 77 | }; 78 | 79 | const googleAuth = async (req, res, next) => { 80 | const { name, email, googlePhotoUrl } = req.body; 81 | try { 82 | const user = await USER.findOne({ email: email }); 83 | if (user) { 84 | const token = jwt.sign({ id: user._id, isAdmin: user.isAdmin }, process.env.JWT_SECRETKEY); 85 | const { password, ...rest } = user._doc; 86 | res 87 | .status(200) 88 | .cookie("access_token", token, { httpOnly: true }) 89 | .json(rest); 90 | } else { 91 | const genratedPassword = 92 | Math.random().toString(36).slice(-8) + 93 | Math.random().toString(36).slice(-8); 94 | const hashedPassword = bcrypt.hashSync(genratedPassword, 10); 95 | const newUser = new USER({ 96 | username: name.toLowerCase().split(' ').join('') + Math.random().toString(9).slice(-4), 97 | email: email, 98 | password: hashedPassword, 99 | profilePicture: googlePhotoUrl 100 | }); 101 | await newUser.save(); 102 | const token = jwt.sign({ id: newUser._id, isAdmin: newUser.isAdmin }, process.env.JWT_SECRETKEY); 103 | const { password, ...rest } = newUser._doc; 104 | res 105 | .status(200) 106 | .cookie("access_token", token, { httpOnly: true }) 107 | .json(rest); 108 | } 109 | } catch (error) { 110 | next(error); 111 | } 112 | }; 113 | 114 | module.exports = { signup, signin, googleAuth }; 115 | -------------------------------------------------------------------------------- /backend/controller/post.controller.js: -------------------------------------------------------------------------------- 1 | const POST = require("../model/post.model"); 2 | const errorHandler = require("../utils/error"); 3 | 4 | const create = async (req, res, next) => { 5 | if (!req.user.isAdmin) { 6 | return next( 7 | errorHandler(403, "You do not have permission to create a post") 8 | ); 9 | } 10 | if (!req.body.title || !req.body.content) { 11 | return next(errorHandler(400, "please provide all required fields")); 12 | } 13 | const slug = req.body.title 14 | .split(" ") 15 | .join("-") 16 | .toLowerCase() 17 | .replace(/[^a-zA-Z0-9-]/g, ""); 18 | const newPost = new POST({ 19 | userId: req.user.id, 20 | slug: slug, 21 | ...req.body, 22 | }); 23 | 24 | try { 25 | const savePost = await newPost.save(); 26 | res.status(201).json(savePost); 27 | } catch (error) { 28 | next(error); 29 | } 30 | }; 31 | 32 | const getPosts = async (req, res, next) => { 33 | try { 34 | const startIndex = parseInt(req.query.startIndex) || 0; 35 | const limit = parseInt(req.query.limit) || 10; 36 | const sortDirection = req.query.order === "asc" ? 1 : -1; 37 | const posts = await POST.find({ 38 | ...(req.query.userId && { userId: req.query.userId }), 39 | ...(req.query.category && { category: req.query.category }), 40 | ...(req.query.slug && { slug: req.query.slug }), 41 | ...(req.query.postId && { _id: req.query.postId }), 42 | ...(req.query.searchTerm && { 43 | $or: [ 44 | { title: { $regex: req.query.searchTerm, $options: "i" } }, 45 | { content: { $regex: req.query.searchTerm, $options: "i" } }, 46 | ], 47 | }), 48 | }) 49 | .sort({ updatedAt: sortDirection }) 50 | .skip(startIndex) 51 | .limit(limit); 52 | 53 | const totalPosts = await POST.countDocuments(); 54 | 55 | const now = new Date(); 56 | const oneMonthAgo = new Date( 57 | now.getFullYear(), 58 | now.getMonth() - 1, 59 | now.getDate() 60 | ); 61 | 62 | const lastMonthPosts = await POST.countDocuments({ 63 | createdAt: { $gte: oneMonthAgo }, 64 | }); 65 | 66 | res.status(200).json({ 67 | posts, 68 | totalPosts, 69 | lastMonthPosts, 70 | }); 71 | } catch (error) { 72 | console.log("error :- ", error.message); 73 | next(error); 74 | } 75 | }; 76 | 77 | const updatePost = async (req, res, next) => { 78 | if (!req.user.isAdmin) { 79 | return next( 80 | errorHandler(403, "You do not have permission to create a post") 81 | ); 82 | } 83 | if (!req.body.title || !req.body.content) { 84 | return next(errorHandler(400, "please provide all required fields")); 85 | } 86 | try { 87 | const updatedPost = await POST.findByIdAndUpdate( 88 | req.params.postId, 89 | { 90 | $set: { 91 | title: req.body.title, 92 | content: req.body.content, 93 | category: req.body.category, 94 | image: req.body.image, 95 | slug: req.body.title 96 | .split(" ") 97 | .join("-") 98 | .toLowerCase() 99 | .replace(/[^a-zA-Z0-9-]/g, ""), 100 | }, 101 | }, 102 | { new: true } 103 | ); 104 | 105 | res.status(200).json(updatedPost); 106 | } catch (error) { 107 | next(error); 108 | } 109 | }; 110 | 111 | const deletePost = async (req, res , next) => { 112 | if(!req.user.isAdmin || req.user.id !== req.params.userId){ 113 | return next(errorHandler(403, "You do not have permission to delete a post")); 114 | } 115 | try { 116 | await POST.findByIdAndDelete(req.params.postId); 117 | res.status(200).json('Post has been deleted successfully'); 118 | } catch (error) { 119 | next(error); 120 | } 121 | } 122 | 123 | module.exports = { create, getPosts, deletePost , updatePost}; 124 | -------------------------------------------------------------------------------- /backend/controller/comment.controller.js: -------------------------------------------------------------------------------- 1 | const COMMENT = require("../model/comment.model"); 2 | const errorHandler = require("../utils/error"); 3 | 4 | const createComment = async (req, res, next) => { 5 | try { 6 | const { comment, postId, userId } = req.body; 7 | if (userId !== req.user.id) { 8 | return next( 9 | errorHandler(403, " You are not allowed to create a comment") 10 | ); 11 | } 12 | 13 | const newComment = new COMMENT({ 14 | content: comment, 15 | postId, 16 | userId, 17 | }); 18 | 19 | await newComment.save(); 20 | res.status(201).json({ newComment }); 21 | } catch (error) { 22 | next(error); 23 | } 24 | }; 25 | 26 | const getPostsComment = async (req, res, next) => { 27 | try { 28 | const comments = await COMMENT.find({ postId: req.params.postId }).sort({ 29 | createAt: -1, 30 | }); 31 | res.status(200).json(comments); 32 | } catch (error) { 33 | next(error); 34 | } 35 | }; 36 | 37 | const likeComment = async (req, res, next) => { 38 | try { 39 | const comment = await COMMENT.findById(req.params.commentId); 40 | if (!comment) { 41 | return next(errorHandler(404, "Comment not found")); 42 | } 43 | const userIndex = await comment.likes.indexOf(req.user.id); 44 | if (userIndex === -1) { 45 | comment.numberofLikes += 1; 46 | comment.likes.push(req.user.id); 47 | } else { 48 | comment.numberofLikes -= 1; 49 | comment.likes.splice(userIndex, 1); 50 | } 51 | await comment.save(); 52 | res.status(200).json(comment); 53 | } catch (error) { 54 | next(error); 55 | } 56 | }; 57 | 58 | const editComment = async (req, res, next) => { 59 | try { 60 | const comment = await COMMENT.findById(req.params.commentId); 61 | if (!comment) { 62 | return next(errorHandler(404, "Comment not found")); 63 | } 64 | if (comment.userId !== req.user.id && !req.user.isAdmin) { 65 | return next( 66 | errorHandler(403, "You are not allowed to edit this comment") 67 | ); 68 | } 69 | const editedComment = await COMMENT.findByIdAndUpdate( 70 | req.params.commentId, 71 | { 72 | content: req.body.content, 73 | }, 74 | { new: true } 75 | ); 76 | res.status(200).json(editedComment); 77 | } catch (error) { 78 | next(error); 79 | } 80 | }; 81 | 82 | const deleteComment = async (req, res, next) => { 83 | try { 84 | const comment = await COMMENT.findById(req.params.commentId); 85 | if (!comment) { 86 | return next(errorHandler(404, "Comment not found")); 87 | } 88 | if (comment.userId !== req.user.id && !req.user.isAdmin) { 89 | return next( 90 | errorHandler(403, "You are not allowed to edit this comment") 91 | ); 92 | } 93 | await COMMENT.findByIdAndDelete(req.params.commentId); 94 | res.status(200).json("Comment has been deleted"); 95 | } catch (error) { 96 | next(error); 97 | } 98 | }; 99 | 100 | const getComments = async (req, res, next) => { 101 | if (!req.user.isAdmin) { 102 | return next(errorHandler(403, " You are not allowed to get all comments")); 103 | } 104 | try { 105 | const startIndex = parseInt(req.query.startIndex) || 0; 106 | const limit = parseInt(req.query.limit) || 9; 107 | const sortDirection = req.query.sort === "'desc" ? -1 : 1; 108 | const comments = await COMMENT.find() 109 | .sort({ createdAt: sortDirection }) 110 | .skip(startIndex) 111 | .limit(limit); 112 | 113 | const totalComments = await COMMENT.countDocuments(); 114 | const now = new Date(); 115 | 116 | const oneMonth = new Date( 117 | now.getFullYear(), 118 | now.getMonth() - 1, 119 | now.getDate() 120 | ); 121 | 122 | const lastMonthComments = await COMMENT.countDocuments({ 123 | createdAt: { $gte: oneMonth }, 124 | }); 125 | 126 | res.status(200).json({ comments, totalComments, lastMonthComments }); 127 | } catch (error) { 128 | next(error); 129 | } 130 | }; 131 | 132 | module.exports = { 133 | createComment, 134 | getPostsComment, 135 | likeComment, 136 | editComment, 137 | deleteComment, 138 | getComments, 139 | }; 140 | -------------------------------------------------------------------------------- /frontend/src/pages/SignIn.jsx: -------------------------------------------------------------------------------- 1 | import { Alert, Button, Label, Spinner, TextInput } from "flowbite-react"; 2 | import { useState } from "react"; 3 | import { Link, useNavigate } from "react-router-dom"; 4 | import { useDispatch, useSelector } from "react-redux"; 5 | import { 6 | SignInStart, 7 | SignInSuccess, 8 | SignInFailure, 9 | } from "../redux/reducers/user/userSlice"; 10 | import OAuth from "../components/OAuth"; 11 | 12 | export default function SignIn() { 13 | const navigate = useNavigate(); 14 | const dispatch = useDispatch(); 15 | const { loading, error: errorMessage } = useSelector((state) => state.user); 16 | const [formData, setFormData] = useState({}); 17 | const handleChange = (event) => { 18 | setFormData({ 19 | ...formData, 20 | [event.target.id]: event.target.value.trim(), 21 | }); 22 | }; 23 | const handleSubmit = async (event) => { 24 | event.preventDefault(); 25 | if (!formData.email || !formData.password) { 26 | return dispatch(SignInFailure("please fill all the required fields")); 27 | } 28 | try { 29 | dispatch(SignInStart()); 30 | const res = await fetch("/api/auth/signin", { 31 | method: "POST", 32 | headers: { "Content-Type": "application/json" }, 33 | body: JSON.stringify(formData), 34 | }); 35 | const data = await res.json(); 36 | if (data.success === false) { 37 | dispatch(SignInFailure(data.message)); 38 | } 39 | if (res.ok) { 40 | dispatch(SignInSuccess(data)); 41 | navigate("/"); 42 | } 43 | } catch (err) { 44 | dispatch(SignInFailure(err.message)); 45 | } 46 | }; 47 | return ( 48 |
49 |
50 | {/* left Side Conatnier */} 51 |
52 | 53 | 54 | Nitin's 55 | 56 | Blog 57 | 58 |

59 | Welcome to our blog community! Signing up is your first step towards 60 | unlocking a world of knowledge, creativity, and connection. 61 |

62 |
63 | {/* Right Side Conatnier */} 64 |
65 |
70 |
71 |
79 |
80 |
89 | 103 | 104 | 105 |
106 | Don't have an account ? 107 | 108 | Sign Up 109 | 110 |
111 | {errorMessage && ( 112 | 113 | {errorMessage} 114 | 115 | )} 116 |
117 |
118 |
119 | ); 120 | } 121 | -------------------------------------------------------------------------------- /backend/controller/user.controller.js: -------------------------------------------------------------------------------- 1 | const errorHandler = require("../utils/error"); 2 | const bcrypt = require("bcrypt"); 3 | const USER = require("../model/users.model"); 4 | 5 | const test = async (req, res) => { 6 | res.json({ 7 | message: "test message", 8 | }); 9 | }; 10 | 11 | const updateUser = async (req, res, next) => { 12 | if (req.user.id !== req.params.userId) { 13 | return next( 14 | errorHandler(403, "You are not allowed to allow to update this user") 15 | ); 16 | } 17 | if (req.body.password) { 18 | if (req.body.password.length < 6) { 19 | next(errorHandler(403, "Password must be at least 6 characters")); 20 | } 21 | req.body.password = bcrypt.hashSync(req.body.password, 10); 22 | } 23 | if (req.body.username) { 24 | if (req.body.username.length < 5 || req.body.username.length > 20) { 25 | return next( 26 | errorHandler(403, "Username must be in between 5 to 20 characters") 27 | ); 28 | } 29 | if (req.body.username.includes(" ")) { 30 | return next(errorHandler(403, "Username cannot contain spaces")); 31 | } 32 | if (!req.body.username === req.body.username.toLowerCase()) { 33 | return next(errorHandler(403, "Username should be in lowercase")); 34 | } 35 | if (!req.body.username.match(/^[a-zA-Z0-9]+$/)) { 36 | return next( 37 | errorHandler(403, "Username only contains letters and numbers") 38 | ); 39 | } 40 | } 41 | try { 42 | const updateUser = await USER.findOneAndUpdate( 43 | { _id: req.params.userId }, 44 | { 45 | $set: { 46 | username: req.body.username, 47 | email: req.body.email, 48 | password: req.body.password, 49 | profilePicture: req.body.profilePicture, 50 | }, 51 | }, 52 | { new: true } 53 | ); 54 | const { password, ...rest } = updateUser._doc; 55 | res.status(200).json(rest); 56 | } catch (error) { 57 | next(error); 58 | } 59 | }; 60 | 61 | const deleteUser = async (req, res, next) => { 62 | if (!req.user.isAdmin && req.user.id !== req.params.userId) { 63 | return next( 64 | errorHandler(403, "You are not allowed to allow to delete this Account") 65 | ); 66 | } 67 | try { 68 | await USER.findByIdAndDelete(req.params.userId); 69 | res.status(200).json("User has been deleted Successfully"); 70 | } catch (error) { 71 | next(error); 72 | } 73 | }; 74 | 75 | const signOut = async (req, res) => { 76 | try { 77 | res 78 | .clearCookie("access_token") 79 | .status(200) 80 | .json("User has been signed out Successfully"); 81 | } catch (error) { 82 | next(error); 83 | } 84 | }; 85 | 86 | const getUsers = async (req, res, next) => { 87 | if (!req.user.isAdmin) { 88 | return next(errorHandler(403, "You are not allowed to see all users")); 89 | } 90 | try { 91 | const startIndex = parseInt(req.query.startIndex) || 0; 92 | const limit = parseInt(req.query.limit) || 10; 93 | const sortDirection = req.query.sortDirection === "asc" ? 1 : -1; 94 | 95 | const users = await USER.find() 96 | .skip(startIndex) 97 | .limit(limit) 98 | .sort({ username: sortDirection }); 99 | 100 | const userWithPassword = users.map((user) => { 101 | const { password, ...rest } = user._doc; 102 | return rest; 103 | }); 104 | 105 | const totalUsers = await USER.countDocuments(); 106 | 107 | const now = new Date(); 108 | 109 | const oneMonthAgo = new Date( 110 | now.getFullYear(), 111 | now.getMonth() - 1, 112 | now.getDate() 113 | ); 114 | 115 | const lastMonthUsers = await USER.countDocuments({ 116 | createdAt: { $gte: oneMonthAgo }, 117 | }); 118 | 119 | res.status(200).json({ 120 | users: userWithPassword, 121 | totalUsers, 122 | lastMonthUsers: lastMonthUsers, 123 | }); 124 | } catch (error) { 125 | next(error); 126 | } 127 | }; 128 | 129 | const getUser = async (req, res, next) => { 130 | try { 131 | const user = await USER.findById(req.params.userId); 132 | const { password, ...rest } = user._doc; 133 | if (!user) { 134 | return next(errorHandler(404, "User not found")); 135 | } 136 | res.status(200).json(rest); 137 | } catch (error) { 138 | next(error); 139 | } 140 | }; 141 | 142 | module.exports = { test, updateUser, deleteUser, signOut, getUsers, getUser }; 143 | -------------------------------------------------------------------------------- /frontend/src/pages/SignUp.jsx: -------------------------------------------------------------------------------- 1 | import { Alert, Button, Label, Spinner, TextInput } from "flowbite-react"; 2 | import { useState } from "react"; 3 | import { Link, useNavigate } from "react-router-dom"; 4 | import OAuth from "../components/OAuth"; 5 | 6 | export default function SignUp() { 7 | const navigate = useNavigate(); 8 | const [formData, setFormData] = useState({}); 9 | const [errorMessage, setErrorMessage] = useState(null); 10 | const [loading, setLoading] = useState(false); 11 | const handleChange = (event) => { 12 | setFormData({ 13 | ...formData, 14 | [event.target.id]: event.target.value.trim(), 15 | }); 16 | }; 17 | const handleSubmit = async (event) => { 18 | event.preventDefault(); 19 | if (!formData.username || !formData.email || !formData.password) { 20 | return setErrorMessage("please fill all the required fields"); 21 | } 22 | try { 23 | setLoading(true); 24 | setErrorMessage(null); 25 | const res = await fetch("/api/auth/signup", { 26 | method: "POST", 27 | headers: { "Content-Type": "application/json" }, 28 | body: JSON.stringify(formData), 29 | }); 30 | const data = await res.json(); 31 | if (data.success === false) { 32 | setLoading(false); 33 | return setErrorMessage(data.message); 34 | } 35 | if (res.ok) { 36 | navigate("/sign-in"); 37 | } 38 | setLoading(false); 39 | } catch (err) { 40 | setErrorMessage(err.message); 41 | setLoading(false); 42 | } 43 | }; 44 | return ( 45 |
46 |
47 | {/* left Side Conatnier */} 48 |
49 | 50 | 51 | Nitin's 52 | 53 | Blog 54 | 55 |

56 | Welcome to our blog community! Signing up is your first step towards 57 | unlocking a world of knowledge, creativity, and connection. 58 |

59 |
60 | {/* Right Side Conatnier */} 61 |
62 |
67 |
68 |
76 |
77 |
85 |
86 |
95 | 109 | 110 | 111 |
112 | have an account ? 113 | 114 | Sign In 115 | 116 |
117 | {errorMessage && ( 118 | 119 | {errorMessage} 120 | 121 | )} 122 |
123 |
124 |
125 | ); 126 | } 127 | -------------------------------------------------------------------------------- /frontend/src/components/Header.jsx: -------------------------------------------------------------------------------- 1 | import { Avatar, Button, Dropdown, Navbar, TextInput } from "flowbite-react"; 2 | import { Link, useLocation, useNavigate } from "react-router-dom"; 3 | import { AiOutlineSearch } from "react-icons/ai"; 4 | import { FaMoon, FaSun } from "react-icons/fa"; 5 | import { useDispatch, useSelector } from "react-redux"; 6 | import { toogletheme } from "../redux/reducers/theme/themeSlice"; 7 | import { SignOutSucess } from "../redux/reducers/user/userSlice"; 8 | import { useEffect, useState } from "react"; 9 | 10 | export default function Header() { 11 | const path = useLocation().pathname; 12 | const location = useLocation(); 13 | const navigate = useNavigate(); 14 | const dispatch = useDispatch(); 15 | const { theme } = useSelector((state) => state.theme); 16 | const { currentUser } = useSelector((state) => state.user); 17 | const [searchTerm, setSearchTerm] = useState(""); 18 | 19 | useEffect(() => { 20 | const urlParams = new URLSearchParams(location.search); 21 | const searchTermFromUrl = urlParams.get("searchTerm"); 22 | if (searchTermFromUrl) { 23 | setSearchTerm(searchTermFromUrl); 24 | } 25 | }, [location.search]); 26 | 27 | const handleSignOut = async () => { 28 | try { 29 | const res = await fetch("/api/user/signout", { 30 | method: "POST", 31 | }); 32 | const data = await res.json(); 33 | if (!res.ok) { 34 | console.log(data.message); 35 | } else { 36 | dispatch(SignOutSucess()); 37 | } 38 | } catch (error) { 39 | console.log(error.message); 40 | } 41 | }; 42 | 43 | const handleSubmit = (e) => { 44 | e.preventDefault(); 45 | const urlParams = new URLSearchParams(location.search); 46 | urlParams.set("searchTerm", searchTerm); 47 | const searchQuery = urlParams.toString(); 48 | navigate(`/search?${searchQuery}`); 49 | }; 50 | 51 | return ( 52 | 53 | 57 | 58 | Nitin's 59 | 60 | Blog 61 | 62 |
63 | setSearchTerm(e.target.value)} 70 | /> 71 | 72 | 75 |
76 | 84 | {currentUser ? ( 85 | 94 | } 95 | > 96 | 97 | @{currentUser.username} 98 | 99 | {currentUser.email} 100 | 101 | 102 | 103 | Profile 104 | 105 | 106 | Sign Out 107 | 108 | ) : ( 109 | 110 | 113 | 114 | )} 115 | 116 |
117 | 118 | 119 | Home 120 | 121 | 122 | About 123 | 124 | 125 | Projects 126 | 127 | 128 |
129 | ); 130 | } 131 | -------------------------------------------------------------------------------- /frontend/src/components/Comment.jsx: -------------------------------------------------------------------------------- 1 | import PropTypes from "prop-types"; 2 | import { useEffect, useState } from "react"; 3 | import moment from "moment"; 4 | import { FaThumbsUp } from "react-icons/fa"; 5 | import { useSelector } from "react-redux"; 6 | import { Button, Textarea } from "flowbite-react"; 7 | 8 | export default function Comment({ comment, onLike, onEdit, onDelte }) { 9 | const [user, setuser] = useState({}); 10 | const { currentUser } = useSelector((state) => state.user); 11 | const [editedContent, setEditedContent] = useState(comment.content); 12 | const [isEditing, setIsEditing] = useState(false); 13 | 14 | useEffect(() => { 15 | const getUsers = async () => { 16 | try { 17 | const res = await fetch(`/api/user/${comment.userId}`); 18 | const data = await res.json(); 19 | if (res.ok) { 20 | setuser(data); 21 | } 22 | } catch (error) { 23 | console.log(error); 24 | } 25 | }; 26 | getUsers(); 27 | }, [comment]); 28 | 29 | const handleEdit = async () => { 30 | setIsEditing(true); 31 | setEditedContent(comment.content); 32 | }; 33 | 34 | const handleSave = async () => { 35 | try { 36 | const res = await fetch(`/api/comment/editComment/${comment._id}`, { 37 | method: "PUT", 38 | headers: { 39 | "Content-Type": "application/json", 40 | }, 41 | body: JSON.stringify({ content: editedContent }), 42 | }); 43 | 44 | if (res.ok) { 45 | onEdit(comment, editedContent); 46 | setIsEditing(false); 47 | } 48 | } catch (error) { 49 | console.log(error); 50 | } 51 | }; 52 | 53 | return ( 54 |
55 |
56 | {user.username} 61 |
62 |
63 |
64 | 65 | {user ? `@ ${user.username}` : `anonymous user`} 66 | 67 | 68 | {moment(comment.createdAt).fromNow()} 69 | 70 |
71 | {isEditing ? ( 72 | <> 73 |