├── .gitignore
├── README.md
├── client
├── package-lock.json
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── robots.txt
├── src
│ ├── App.js
│ ├── assets
│ │ ├── DevStack-Home.png
│ │ ├── DevStack-Login.png
│ │ ├── contact_app.png
│ │ └── google.png
│ ├── components
│ │ ├── BlogForm.js
│ │ ├── Blogs
│ │ │ ├── BlogCard.js
│ │ │ ├── BlogDetails.js
│ │ │ └── BlogsList.js
│ │ ├── Comments
│ │ │ ├── CommentSection.js
│ │ │ └── CommentsList.js
│ │ ├── Layout
│ │ │ ├── WithNavbar.js
│ │ │ └── WithoutNavbar.js
│ │ ├── LoadingComponent.js
│ │ ├── Navbar
│ │ │ ├── NavbarProfileDroddown.js
│ │ │ ├── SearchBar.js
│ │ │ └── index.js
│ │ ├── Routes
│ │ │ ├── AnonymousRoute.js
│ │ │ └── PrivateRoute.js
│ │ ├── TagsList.js
│ │ └── UserForm
│ │ │ ├── LoginForm.js
│ │ │ └── SignUpForm.js
│ ├── helpers
│ │ ├── removeTags.js
│ │ └── useOutsideClick.js
│ ├── index.css
│ ├── index.js
│ ├── pages
│ │ ├── AddBlog.js
│ │ ├── Blog.js
│ │ ├── Bookmarks.js
│ │ ├── EditBlog.js
│ │ ├── LandingPage.js
│ │ ├── LoginPage.js
│ │ ├── SearchedBlogs.js
│ │ ├── UserPublishedBlogsList.js
│ │ └── index.js
│ └── redux
│ │ ├── Api.js
│ │ ├── checkForTokenExpiry.js
│ │ ├── slices
│ │ ├── authSlice.js
│ │ ├── blogsSlice.js
│ │ └── userSlice.js
│ │ └── store.js
└── tailwind.config.js
└── server
├── controllers
├── blogs.js
└── user.js
├── index.js
├── middleware
└── auth.js
├── models
├── Blog.js
├── Tag.js
└── User.js
├── package-lock.json
├── package.json
└── routes
├── blogs.js
└── users.js
/.gitignore:
--------------------------------------------------------------------------------
1 | server/node_modules
2 | server/.env
3 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
4 |
5 | # dependencies
6 | /client/node_modules
7 | /client/.pnp
8 | /client.pnp.js
9 |
10 | # testing
11 | /client/coverage
12 |
13 | # production
14 | /client/build
15 |
16 | # misc
17 | .DS_Store
18 | /client/.env
19 |
20 | /client/.env.local
21 | /client/.env.development.local
22 | /client/.env.test.local
23 | /client/.env.production.local
24 |
25 | npm-debug.log*
26 | yarn-debug.log*
27 | yarn-error.log*
28 |
29 | OAuth.json
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DevStack
2 |
3 | 
4 | 
5 |
--------------------------------------------------------------------------------
/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "client",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@react-oauth/google": "^0.11.1",
7 | "@reduxjs/toolkit": "^1.9.5",
8 | "@testing-library/jest-dom": "^5.17.0",
9 | "@testing-library/react": "^13.4.0",
10 | "@testing-library/user-event": "^13.5.0",
11 | "axios": "^1.4.0",
12 | "jwt-decode": "^3.1.2",
13 | "react": "^18.2.0",
14 | "react-dom": "^18.2.0",
15 | "react-icons": "^4.10.1",
16 | "react-quill": "^2.0.0",
17 | "react-redux": "^8.1.1",
18 | "react-router-dom": "^6.14.2",
19 | "react-scripts": "5.0.1",
20 | "react-scroll": "^1.8.9",
21 | "react-toastify": "^9.1.3",
22 | "tailwindcss": "^3.3.3",
23 | "web-vitals": "^2.1.4"
24 | },
25 | "scripts": {
26 | "start": "react-scripts start",
27 | "build": "react-scripts build",
28 | "test": "react-scripts test",
29 | "eject": "react-scripts eject"
30 | },
31 | "eslintConfig": {
32 | "extends": [
33 | "react-app",
34 | "react-app/jest"
35 | ]
36 | },
37 | "browserslist": {
38 | "production": [
39 | ">0.2%",
40 | "not dead",
41 | "not op_mini all"
42 | ],
43 | "development": [
44 | "last 1 chrome version",
45 | "last 1 firefox version",
46 | "last 1 safari version"
47 | ]
48 | }
49 | }
--------------------------------------------------------------------------------
/client/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shishpal0666/DevStack/35b6010b16b6862e1868457808fb6df5dd955bd8/client/public/favicon.ico
--------------------------------------------------------------------------------
/client/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
19 |
20 |
21 |
30 | DevStack
31 |
32 |
33 |
34 |
35 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/client/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shishpal0666/DevStack/35b6010b16b6862e1868457808fb6df5dd955bd8/client/public/logo192.png
--------------------------------------------------------------------------------
/client/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shishpal0666/DevStack/35b6010b16b6862e1868457808fb6df5dd955bd8/client/public/logo512.png
--------------------------------------------------------------------------------
/client/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/client/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/client/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import { BrowserRouter as Router } from 'react-router-dom';
3 | import Pages from "./pages";
4 | import { GoogleOAuthProvider } from '@react-oauth/google';
5 | import { getUserSuccess } from "./redux/slices/authSlice";
6 | import { useDispatch, useSelector } from "react-redux";
7 | import { getBookmarkedBlogs, getPopularTopicsList, getAllBlogs, getBookmarkedBlogsId } from "./redux/slices/blogsSlice";
8 | import { ToastContainer } from "react-toastify";
9 |
10 | function App() {
11 | const dispatch = useDispatch();
12 | const userData = localStorage.getItem("blog-user");
13 | const {email} = useSelector((store) => store.auth.userData);
14 |
15 | useEffect(() => {
16 | dispatch(getUserSuccess(JSON.parse(userData)));
17 | dispatch(getPopularTopicsList());
18 | dispatch(getBookmarkedBlogsId());
19 | dispatch(getAllBlogs());
20 | },[email])
21 |
22 | return (
23 |
24 |
35 |
36 | );
37 | }
38 |
39 | export default App;
40 |
--------------------------------------------------------------------------------
/client/src/assets/DevStack-Home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shishpal0666/DevStack/35b6010b16b6862e1868457808fb6df5dd955bd8/client/src/assets/DevStack-Home.png
--------------------------------------------------------------------------------
/client/src/assets/DevStack-Login.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shishpal0666/DevStack/35b6010b16b6862e1868457808fb6df5dd955bd8/client/src/assets/DevStack-Login.png
--------------------------------------------------------------------------------
/client/src/assets/contact_app.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shishpal0666/DevStack/35b6010b16b6862e1868457808fb6df5dd955bd8/client/src/assets/contact_app.png
--------------------------------------------------------------------------------
/client/src/assets/google.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shishpal0666/DevStack/35b6010b16b6862e1868457808fb6df5dd955bd8/client/src/assets/google.png
--------------------------------------------------------------------------------
/client/src/components/BlogForm.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react'
2 | import ReactQuill from 'react-quill';
3 | import { useDispatch } from 'react-redux';
4 | import { useNavigate } from 'react-router-dom';
5 | import { createNewBlog, updateBlog, getAllBlogs } from '../redux/slices/blogsSlice';
6 | import { GrFormClose } from 'react-icons/gr';
7 |
8 | const modules = {
9 | toolbar: [
10 | [{ header: '1' }, { header: '2' }, { header: [3, 4] }],
11 | [{ 'size': [ 'normal'] }],
12 | ['bold', 'italic', 'underline', 'strike', 'blockquote',],
13 | [{ 'script': 'sub'}, { 'script': 'super' }],
14 | [{ 'color': [] }, { 'background': [] }],
15 | [{ 'font': [] }],
16 | [
17 | { list: 'ordered' },
18 | { list: 'bullet' },
19 | { indent: '-1' },
20 | { indent: '+1' },
21 | ],
22 | ['link', 'image', 'video', 'formula', 'code-block'],
23 | ['clean'],
24 | ],
25 | clipboard: {
26 | matchVisual: false,
27 | },
28 | }
29 |
30 | const BlogForm = ({type, authorId, blogId, initialTitle= '', initialContent='', initialTagList=[]}) => {
31 |
32 | const navigate = useNavigate();
33 | const dispatch = useDispatch();
34 | const [title, setTitle] = useState(initialTitle);
35 | const [content, setContent] = useState(initialContent);
36 | const [tag, setTag] = useState();
37 | const [tagList, setTagsList] = useState(initialTagList);
38 |
39 | const handleEnterKey = (event) => {
40 | if(event.key === "Enter"){
41 | if(tag !== ''){
42 | setTagsList((tagList) => (
43 | [...tagList , tag.toLowerCase().trim()]
44 | ))
45 | }
46 | setTag('');
47 | }
48 | }
49 |
50 | const gotoIndexPage = () => {
51 | navigate("/", { replace: true });
52 | dispatch(getAllBlogs());
53 | }
54 |
55 | const handlePostButton = () => {
56 | if(type === 'add'){
57 | dispatch(createNewBlog({title, content, tagList}, gotoIndexPage));
58 | }
59 | else{
60 | dispatch(updateBlog({title, content, tags:tagList, blogId, authorId}, gotoIndexPage));
61 | }
62 | }
63 |
64 | const handleRemoveTagButton = (tag) => {
65 | setTagsList((tagsList) => {
66 | const newTagsList = tagsList.filter((item) => item !== tag);
67 |
68 | return newTagsList;
69 | })
70 | }
71 |
72 | return (
73 | <>
74 |
75 |
76 |
88 |
89 |
90 |
91 |
{setTag(e.target.value)}}
95 | onKeyDown={handleEnterKey}
96 | className="w-[80%] p-5 text-lg border-[1px] border-solid border-[#cac7c7] outline-none placeholder:text-2xl placeholder:font-medium placeholder:text-[#777373] focus:"
97 | placeholder="Add Tags"
98 | />
99 |
100 | {
101 | tagList?.map((tag) => {
102 | return(
103 | <>
104 |
105 | {tag}
106 |
109 |
110 | >
111 | )
112 | })
113 | }
114 |
115 |
116 |
117 | >
118 | )
119 | }
120 |
121 | export default BlogForm;
122 |
--------------------------------------------------------------------------------
/client/src/components/Blogs/BlogCard.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react'
2 | import { BiSolidBookmark } from 'react-icons/bi';
3 | import { useDispatch, useSelector } from 'react-redux';
4 | import { Link } from 'react-router-dom';
5 | import Img from '../../assets/contact_app.png';
6 | import { removeTags } from '../../helpers/removeTags';
7 | import { BookmarkBlog } from '../../redux/slices/blogsSlice';
8 | import { toast } from "react-toastify";
9 |
10 | const BlogCard = ({ _id, title, content, createdAt, author, tags = null }) => {
11 |
12 | const dispatch = useDispatch();
13 | const plainContent = removeTags(content);
14 |
15 | const { email } = useSelector((store) => store.auth.userData);
16 | const { bookmarkedBlogsId, bookmarkedBlogs } = useSelector((store) => store.blog);
17 | const [isBookmarked, setIsBookmarked] = useState(false);
18 |
19 | useEffect(() => {
20 | setIsBookmarked((isBookmarked) => {
21 | return bookmarkedBlogsId.includes(_id)
22 | })
23 | }, [bookmarkedBlogsId])
24 |
25 | const handleBookmarkButton = () => {
26 | if (email) {
27 | dispatch(BookmarkBlog(_id));
28 | }
29 | else {
30 | toast.info('Please Login');
31 | }
32 | }
33 |
34 | return (
35 |
36 |
39 |
40 |
41 |
42 |
43 | {
44 | author?.imgUrl
45 | ?
46 |

47 | :
48 |
{author?.name.charAt(0)}
49 | }
50 |
51 | {author.name}
52 | •
53 | {createdAt.split('T')[0]}
54 |
55 |
56 |
57 |
58 |
59 | {title}
60 | {plainContent}
61 |
62 | {/*
63 |
64 | */}
65 |
66 |
67 |
68 | {
69 | tags?.map((tag) => {
70 | return (
71 | <>
72 | {/* in case of passing query in link tag */}
73 |
74 | {tag}
75 |
76 | >
77 | )
78 | })
79 | }
80 |
81 |
82 |
83 | )
84 | }
85 |
86 | export default BlogCard;
87 |
--------------------------------------------------------------------------------
/client/src/components/Blogs/BlogDetails.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import { useDispatch, useSelector } from 'react-redux';
3 | import { Link, useNavigate } from 'react-router-dom';
4 | import { MdDelete } from 'react-icons/md';
5 | import { HiPencil } from 'react-icons/hi';
6 | import { BiSolidLike } from 'react-icons/bi';
7 | import { MdOutlineModeComment } from 'react-icons/md';
8 | import { deleteBlog, likeBlog, resetBlogDetails, getAllBlogs } from '../../redux/slices/blogsSlice';
9 | import LoadingComponent from '../LoadingComponent';
10 | import { toast } from 'react-toastify';
11 | import { Link as LinkScroll } from 'react-scroll';
12 | import CommentSection from '../Comments/CommentSection';
13 |
14 | const BlogDetails = () => {
15 |
16 | const dispatch = useDispatch();
17 | const navigate = useNavigate();
18 | const { blogDetails, loading, blogLikesNumber, blogCommentsList } = useSelector((store) => store.blog);
19 | const { userData } = useSelector((store) => store.auth);
20 | const { _id, title, content, createdAt, author, tags, likes } = blogDetails;
21 | const [isLiked, setIsLiked] = useState(false);
22 |
23 | useEffect(() => {
24 | setIsLiked((isLiked) => {
25 | return blogLikesNumber?.includes(userData._id);
26 | })
27 | }, [blogLikesNumber]);
28 |
29 | useEffect(() => {
30 | dispatch(resetBlogDetails());
31 | }, [])
32 |
33 | const gotoIndexPage = () => {
34 | navigate("/", { replace: true });
35 | dispatch(getAllBlogs());
36 | }
37 |
38 | const handleEditButton = () => {
39 | navigate(`/edit-blog/${_id}`);
40 | }
41 |
42 | const handleDeleteButton = () => {
43 | dispatch(deleteBlog(_id, gotoIndexPage));
44 | }
45 |
46 | const handleLikeButton = () => {
47 | if (userData?.email) {
48 | dispatch(likeBlog(_id));
49 | }
50 | else {
51 | toast.info('Please Login');
52 | }
53 | }
54 |
55 | const handleCommentButton = () => {
56 | toast.info('Work in progress');
57 | }
58 |
59 | return (
60 |
61 |
62 | {
63 | loading
64 | ?
65 |
66 | :
67 | _id
68 | ?
69 | <>
70 |
71 |
72 | {title}
73 |
74 | {
75 | author?._id === userData?._id
76 | &&
77 | <>
78 |
79 |
80 | >
81 | }
82 |
83 |
84 |
85 |
86 |
87 | {
88 | author?.imgUrl
89 | ?
90 |

91 | :
92 | author?.name.charAt(0)
93 | &&
94 |
{author?.name.charAt(0)}
95 | }
96 |
97 | {author?.name}
98 |
99 | •
100 | {createdAt?.split('T')[0]}
101 |
102 |
103 |
104 |
105 |
108 | {blogLikesNumber.length}
109 |
110 |
111 |
120 |
121 |
122 | {blogCommentsList.length}
123 |
124 |
125 |
126 |
128 |
129 | {
130 | tags?.map((tag) => {
131 | return (
132 | <>
133 | {/* in case of passing tag as search query params*/}
134 |
135 | {tag}
136 |
137 | >
138 | )
139 | })
140 | }
141 |
142 |
145 |
146 | >
147 | :
148 |
404 Not found
149 | }
150 |
151 |
152 | )
153 | }
154 |
155 | export default BlogDetails;
156 |
--------------------------------------------------------------------------------
/client/src/components/Blogs/BlogsList.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useDispatch, useSelector } from 'react-redux'
3 | import BlogCard from './BlogCard'
4 | import { MdKeyboardDoubleArrowDown } from 'react-icons/md';
5 | import LoadingComponent from '../LoadingComponent';
6 |
7 | const BlogsList = ({blogsData, callback}) => {
8 |
9 | const {blogsList, totalPages, currentPage} = blogsData;
10 |
11 | const { loading } = useSelector((store) => store.blog);
12 | const handleShowMoreButton = () => {
13 | if(totalPages > currentPage){
14 | callback();
15 | }
16 | }
17 |
18 | return (
19 | <>
20 |
21 | {
22 | loading &&
23 | }
24 | {
25 | blogsList.length > 0
26 | ?
27 | <>
28 | {
29 | blogsList?.map((blog, index) => {
30 | return(
31 | <>
32 |
33 |
34 | >
35 | )
36 | })
37 | }
38 | {
39 | totalPages > currentPage
40 | &&
41 |
45 | }
46 | >
47 | :
48 |
49 | {
50 | !loading
51 | &&
52 | No Blog
53 | }
54 |
55 |
56 | }
57 |
58 | >
59 | )
60 | }
61 |
62 | export default BlogsList;
63 |
--------------------------------------------------------------------------------
/client/src/components/Comments/CommentSection.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import CommentsList from './CommentsList';
3 | import ReactQuill from 'react-quill';
4 | import { useDispatch, useSelector } from 'react-redux';
5 | import { postBlogComment } from '../../redux/slices/blogsSlice';
6 |
7 | const modules = {
8 | toolbar: [
9 | ['bold', 'italic', 'underline'],
10 | ['link']
11 | ],
12 | clipboard: {
13 | matchVisual: false,
14 | },
15 | }
16 |
17 | const CommentSection = () => {
18 |
19 | const { _id } = useSelector((store) => store.blog.blogDetails);
20 | const [comment, setComment] = useState('');
21 | const dispatch = useDispatch();
22 |
23 | const handlePostCommentButton = () => {
24 | dispatch(postBlogComment({ blogId: _id, comment }));
25 | setComment('');
26 | }
27 |
28 | return (
29 |
45 | )
46 | }
47 |
48 | export default CommentSection;
49 |
--------------------------------------------------------------------------------
/client/src/components/Comments/CommentsList.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useSelector } from 'react-redux';
3 | import { Link } from 'react-router-dom';
4 |
5 | const CommentsList = () => {
6 |
7 | const { blogCommentsList } = useSelector((store) => store.blog);
8 |
9 | return (
10 |
11 | {
12 | blogCommentsList?.slice(0)?.reverse()?.map((comment) => {
13 | const { _id, name, imgUrl } = comment?.user;
14 | return (
15 | <>
16 |
17 |
18 |
19 |
20 | {
21 | imgUrl
22 | ?
23 |

24 | :
25 |
{name?.charAt(0)}
26 | }
27 |
28 | {name}
29 |
30 |
31 |
32 |
33 |
34 | >
35 | )
36 | })
37 | }
38 |
39 | )
40 | }
41 |
42 | export default CommentsList;
43 |
--------------------------------------------------------------------------------
/client/src/components/Layout/WithNavbar.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Navbar from '../Navbar'
3 | import { Outlet } from 'react-router-dom'
4 |
5 | const WithNavbar = () => {
6 | return (
7 | <>
8 |
9 |
10 | >
11 | )
12 | }
13 |
14 | export default WithNavbar;
15 |
--------------------------------------------------------------------------------
/client/src/components/Layout/WithoutNavbar.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Outlet } from 'react-router-dom'
3 |
4 | const WithoutNavbar = () => {
5 | return (
6 |
7 | )
8 | }
9 |
10 | export default WithoutNavbar;
11 |
--------------------------------------------------------------------------------
/client/src/components/LoadingComponent.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const LoadingComponent = () => {
4 | return (
5 |
8 | )
9 | }
10 |
11 | export default LoadingComponent;
12 |
--------------------------------------------------------------------------------
/client/src/components/Navbar/NavbarProfileDroddown.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef } from 'react'
2 | import { useDispatch, useSelector } from 'react-redux'
3 | import { Link, useLocation, useNavigate } from 'react-router-dom'
4 | import useOutsideClick from '../../helpers/useOutsideClick';
5 | import { removeUserData } from '../../redux/slices/authSlice';
6 |
7 | const NavbarProfileDroddown = ({ closeProfileDropdown }) => {
8 |
9 | const dispatch = useDispatch();
10 | const { name, email } = useSelector((store) => store.auth.userData)
11 | const ref = useRef();
12 | const navigate = useNavigate();
13 | useOutsideClick(ref, () => {
14 | closeProfileDropdown();
15 | });
16 |
17 | const gotoIndexPage = () => {
18 | navigate("/", { replace: true });
19 | }
20 |
21 | const handleSignOut = () => {
22 | dispatch(removeUserData(gotoIndexPage));
23 | }
24 |
25 | return (
26 |
27 |
28 | {name}
29 | {email}
30 |
31 |
32 | -
33 | My Blogs
34 |
35 | -
36 | Bookmarks
37 |
38 | -
39 |
42 |
43 |
44 |
45 | )
46 | }
47 |
48 | export default NavbarProfileDroddown;
49 |
--------------------------------------------------------------------------------
/client/src/components/Navbar/SearchBar.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react'
2 | import { BiSearch } from 'react-icons/bi'
3 | import { useDispatch } from 'react-redux';
4 | import { createSearchParams, useLocation, useNavigate, useSearchParams } from 'react-router-dom';
5 |
6 | const SearchBar = () => {
7 | const dispatch = useDispatch();
8 | const navigate = useNavigate();
9 | const location = useLocation();
10 |
11 | const [searchParams, setSearchParams] = useSearchParams();
12 |
13 | const [searchValue, setSearchValue] = useState(searchParams.get("search") || '');
14 |
15 | const handleSearch = (e) => {
16 | setSearchValue(e.target.value);
17 | }
18 |
19 | useEffect(() => {
20 | if(location.pathname !== '/search'){
21 | setSearchValue('');
22 | }
23 | },[location.pathname])
24 |
25 | // const tag = searchParams.get('tag');
26 | // const handleEnterKey = (e) => {
27 | // if( e.key === 'Enter'){
28 | // if(!tag){
29 | // const query = {
30 | // search: searchValue,
31 | // }
32 | // navigate({
33 | // pathname: '/search',
34 | // search: createSearchParams(query).toString()
35 | // })
36 | // }
37 | // else{
38 | // const query = {
39 | // search: searchValue,
40 | // tag: tag
41 | // }
42 | // navigate({
43 | // pathname: '/search',
44 | // search: createSearchParams(query).toString()
45 | // })
46 | // }
47 | // }
48 | // }
49 |
50 | const handleEnterKey = (e) => {
51 | if(e.key === 'Enter'){
52 | searchValue !== ''
53 | ?
54 | navigate({
55 | pathname: '/search',
56 | search: createSearchParams({search: searchValue}).toString(),
57 | })
58 | :
59 | navigate('/');
60 | }
61 | }
62 |
63 | return (
64 |
65 |
66 | handleSearch(e)}
72 | value={searchValue}/>
73 |
74 | )
75 | }
76 |
77 | export default SearchBar;
78 |
--------------------------------------------------------------------------------
/client/src/components/Navbar/index.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react'
2 | import { Link, useLocation } from 'react-router-dom'
3 | import { AiOutlinePlus } from 'react-icons/ai'
4 | import { MdKeyboardArrowDown } from 'react-icons/md'
5 | import SearchBar from './SearchBar'
6 | import LoginForm from '../UserForm/LoginForm'
7 | import SignUpForm from '../UserForm/SignUpForm'
8 | import NavbarProfileDroddown from './NavbarProfileDroddown'
9 | import { useSelector } from 'react-redux'
10 |
11 | const Navbar = () => {
12 |
13 | const [showLoginForm, setShowLoginForm] = useState(false);
14 | const [showSignUpForm, setShowSignUpForm] = useState(false);
15 | const [showProfileDropdown, setShowProfileDropdown] = useState(false);
16 | const { name, imgUrl } = useSelector((store) => store.auth.userData)
17 | const location = useLocation();
18 |
19 | useEffect(() => {
20 | closeProfileDropdown();
21 | },[location])
22 |
23 | const handleSignInButton = (e) => {
24 | e.stopPropagation();
25 | setShowLoginForm(true);
26 | setShowSignUpForm(false);
27 | }
28 |
29 | const handleSignUpButton = (e) => {
30 | e.stopPropagation();
31 | setShowLoginForm(false);
32 | setShowSignUpForm(true);
33 | }
34 |
35 | const handleCloseModal = () => {
36 | setShowLoginForm(false);
37 | setShowSignUpForm(false);
38 | }
39 |
40 | const handleToggleProfileDropdown = (e) => {
41 | e.stopPropagation();
42 | setShowProfileDropdown(!showProfileDropdown);
43 | }
44 |
45 | const closeProfileDropdown = () => {
46 | setShowProfileDropdown(false);
47 | }
48 |
49 | return (
50 | <>
51 | {
52 | showLoginForm
53 | &&
54 |
55 |
56 |
57 | }
58 | {
59 | showSignUpForm
60 | &&
61 |
62 |
63 |
64 | }
65 |
66 |
67 |
68 |
69 | .blog
70 |
71 |
72 |
73 |
74 |
75 |
79 |
80 |
81 | {
82 | name
83 | ?
84 |
85 | {
86 | showProfileDropdown
87 | &&
88 |
89 |
90 |
91 | }
92 |
104 |
105 | :
106 |
109 | }
110 |
111 |
112 |
113 | >
114 | )
115 | }
116 |
117 | export default Navbar;
118 |
--------------------------------------------------------------------------------
/client/src/components/Routes/AnonymousRoute.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useSelector } from "react-redux";
3 | import { Navigate } from "react-router-dom";
4 |
5 | export const AnonymousRoute = ({ children }) => {
6 | const { email } = useSelector((store) => store.auth.userData)
7 |
8 | return email ? : children;
9 | };
--------------------------------------------------------------------------------
/client/src/components/Routes/PrivateRoute.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useSelector } from "react-redux";
3 | import { Navigate } from "react-router-dom";
4 |
5 | export const PrivateRoute = ({ children }) => {
6 | const userData = localStorage.getItem("blog-user");
7 |
8 | return userData ? children :
9 | };
--------------------------------------------------------------------------------
/client/src/components/TagsList.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useSelector } from 'react-redux';
3 | import { Link } from 'react-router-dom'
4 |
5 | const tagsList = ["React", "Angular", "Git", "Node", "Express", "VS code", "MongoDB", "Javascript", "HTMl", "CSS"]
6 | const TagsList = () => {
7 |
8 | const { mostPopularTopics } = useSelector((store) => store.blog);
9 |
10 | return (
11 |
12 |
13 |
Popular Topics
14 |
15 | {
16 | mostPopularTopics?.map((element) => {
17 | const {_id: tag} = element;
18 | return(
19 | <>
20 | {/* */}
21 |
22 |
23 | {tag}
24 |
25 |
26 | >
27 | )
28 | })
29 | }
30 |
31 |
32 |
33 | )
34 | }
35 |
36 | export default TagsList;
37 |
--------------------------------------------------------------------------------
/client/src/components/UserForm/LoginForm.js:
--------------------------------------------------------------------------------
1 | import React, { useRef, useState } from 'react'
2 | import GoogleIcon from '../../assets/google.png'
3 | import { FaEye, FaEyeSlash } from 'react-icons/fa'
4 | import { useGoogleLogin } from '@react-oauth/google';
5 | import { useDispatch, useSelector } from 'react-redux';
6 | import { loginUser, signUpUserWithGoogle } from '../../redux/slices/authSlice';
7 | import useOutsideClick from '../../helpers/useOutsideClick';
8 | import LoadingComponent from '../LoadingComponent';
9 |
10 | const LoginForm = ({ handleSignUpButton, gotoIndexPage }) => {
11 |
12 | const { loading } = useSelector((store) => store.auth);
13 | const [formData, setFormData] = useState({ email: '', password: '' })
14 | const [showPassword, setShowPassword] = useState(false);
15 | const dispatch = useDispatch();
16 | const ref = useRef();
17 |
18 | useOutsideClick(ref, () => {
19 | gotoIndexPage();
20 | });
21 |
22 | const handleTogglePassword = (e) => {
23 | e.stopPropagation();
24 | setShowPassword(!showPassword);
25 | }
26 |
27 | const handleSubmit = (event) => {
28 | event.preventDefault();
29 | dispatch(loginUser(formData, gotoIndexPage));
30 | }
31 |
32 | const handleChange = (e) => {
33 | const { name, value } = e.target;
34 | setFormData((formData) => ({ ...formData, [name]: value }))
35 | }
36 |
37 | const login = useGoogleLogin({
38 | onSuccess: async (data) => {
39 | dispatch(signUpUserWithGoogle(data, gotoIndexPage))
40 | },
41 | flow: 'auth-code',
42 | })
43 |
44 | return (
45 |
46 | {
47 | loading &&
48 | }
49 |
50 |
LOGIN
51 |
88 |
89 |
93 |
94 | New here ?
95 |
96 |
97 |
98 |
99 |
100 | )
101 | }
102 |
103 | export default LoginForm;
104 |
--------------------------------------------------------------------------------
/client/src/components/UserForm/SignUpForm.js:
--------------------------------------------------------------------------------
1 | import React, { useRef, useState } from 'react'
2 | import GoogleIcon from '../../assets/google.png'
3 | import { FaEye, FaEyeSlash } from 'react-icons/fa'
4 | import axios from 'axios';
5 | import { useGoogleLogin } from '@react-oauth/google';
6 | import { useDispatch, useSelector } from 'react-redux';
7 | import { signUpUser, signUpUserWithGoogle } from '../../redux/slices/authSlice';
8 | import useOutsideClick from '../../helpers/useOutsideClick';
9 | import LoadingComponent from '../LoadingComponent';
10 |
11 | const initialState = {
12 | firstName: '',
13 | lastName: '',
14 | email: '',
15 | password: '',
16 | confirmPassword: '',
17 | }
18 | const SignUpForm = ({ handleSignInButton, gotoIndexPage }) => {
19 |
20 | const { loading } = useSelector((store) => store.auth);
21 | const [showPassword, setShowPassword] = useState(false);
22 | const [showConfirmPassword, setShowConfirmPassword] = useState(false);
23 | const [formData, setFormData] = useState(initialState);
24 | const dispatch = useDispatch();
25 | const ref = useRef();
26 |
27 | useOutsideClick(ref, () => {
28 | gotoIndexPage();
29 | });
30 |
31 | const handleTogglePassword = (e) => {
32 | e.stopPropagation();
33 | setShowPassword(!showPassword);
34 | }
35 |
36 | const handleToggleConfirmPassword = (e) => {
37 | e.stopPropagation();
38 | setShowConfirmPassword(!showConfirmPassword);
39 | }
40 |
41 | const handleSubmit = (event) => {
42 | event.preventDefault();
43 | dispatch(signUpUser(formData, gotoIndexPage));
44 | }
45 |
46 | const handleChange = (e) => {
47 | const { name, value } = e.target;
48 | setFormData((formData) => ({ ...formData, [name]: value }))
49 | }
50 |
51 | const login = useGoogleLogin({
52 | onSuccess: async (data) => {
53 | dispatch(signUpUserWithGoogle(data, gotoIndexPage))
54 | },
55 | flow: 'auth-code',
56 | })
57 |
58 | return (
59 |
60 | {
61 | loading &&
62 | }
63 |
64 |
Sign-Up
65 |
147 |
148 |
152 |
153 | Already here ?
154 |
155 |
156 |
157 |
158 |
159 | )
160 | }
161 |
162 | export default SignUpForm;
163 |
--------------------------------------------------------------------------------
/client/src/helpers/removeTags.js:
--------------------------------------------------------------------------------
1 |
2 | export function removeTags(str) {
3 | if ((str===null) || (str===''))
4 | return false;
5 | else
6 | str = str.toString();
7 |
8 | // Regular expression to identify HTML tags in
9 | // the input string. Replacing the identified
10 | // HTML tag with a null string.
11 | return str.replace( /(<([^>]+)>)/ig, '');
12 | }
--------------------------------------------------------------------------------
/client/src/helpers/useOutsideClick.js:
--------------------------------------------------------------------------------
1 | import { useEffect } from "react";
2 |
3 | const useOutsideClick = (ref, callback) => {
4 | const handleClick = (event) => {
5 | if (ref.current && !ref.current.contains(event.target)) {
6 | callback();
7 | }
8 | };
9 |
10 | useEffect(() => {
11 | document.addEventListener("click", handleClick);
12 |
13 | return () => {
14 | document.removeEventListener("click", handleClick);
15 | };
16 | });
17 | };
18 |
19 | export default useOutsideClick;
--------------------------------------------------------------------------------
/client/src/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 |
3 | @layer base {
4 | h1 {
5 | @apply text-3xl;
6 | }
7 |
8 | h2 {
9 | @apply text-2xl;
10 | }
11 |
12 | h3 {
13 | @apply text-xl;
14 | }
15 |
16 | h4 {
17 | @apply text-lg;
18 | }
19 |
20 | p {
21 | @apply text-base leading-7;
22 | }
23 |
24 | span {
25 | @apply text-base leading-7 tracking-normal;
26 | }
27 | }
28 |
29 | @tailwind components;
30 | @tailwind utilities;
31 |
32 | * {
33 | margin: 0;
34 | padding: 0;
35 | }
36 |
37 | body {
38 | margin: 0;
39 | font-family: 'Open Sans', sans-serif;
40 | }
41 |
42 | html {
43 | scroll-behavior: smooth;
44 | }
45 |
46 | .quill>.ql-container>.ql-editor.ql-blank::before {
47 | font-size: 30px;
48 | font-weight: 400;
49 | color: #a09e9e;
50 | font-style: normal;
51 | }
52 |
53 | .ql-container {
54 | min-height: 300px;
55 | }
56 |
57 | .commentForm .ql-container {
58 | min-height: 100px;
59 | }
60 |
61 | .commentForm .quill>.ql-container>.ql-editor.ql-blank::before {
62 | font-size: 20px;
63 | }
--------------------------------------------------------------------------------
/client/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom/client';
3 | import './index.css';
4 | import App from './App';
5 | import { Provider } from "react-redux";
6 | import { store } from './redux/store.js';
7 | import 'react-toastify/dist/ReactToastify.css';
8 | import 'react-quill/dist/quill.snow.css';
9 |
10 | const root = ReactDOM.createRoot(document.getElementById('root'));
11 | root.render(
12 |
13 |
14 |
15 |
16 | ,
17 | );
--------------------------------------------------------------------------------
/client/src/pages/AddBlog.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import BlogForm from '../components/BlogForm'
3 |
4 | const AddBlog = () => {
5 | return (
6 |
7 |
8 |
9 | )
10 | }
11 |
12 | export default AddBlog;
13 |
--------------------------------------------------------------------------------
/client/src/pages/Blog.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 | import { useDispatch, useSelector } from 'react-redux';
3 | import { useParams } from 'react-router-dom';
4 | import BlogDetails from '../components/Blogs/BlogDetails';
5 | import { getBlogDetails } from '../redux/slices/blogsSlice';
6 |
7 | const Blog = () => {
8 |
9 | const params = useParams();
10 | const dispatch = useDispatch();
11 |
12 | useEffect(() => {
13 | dispatch(getBlogDetails(params.id))
14 | }, [params.id]);
15 |
16 | return (
17 |
18 |
19 |
20 | )
21 | }
22 |
23 | export default Blog;
24 |
--------------------------------------------------------------------------------
/client/src/pages/Bookmarks.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react'
2 | import { useDispatch, useSelector } from 'react-redux'
3 | import BlogsList from '../components/Blogs/BlogsList'
4 | import { getBookmarkedBlogs } from '../redux/slices/blogsSlice';
5 |
6 | const Bookmarks = () => {
7 |
8 | const dispatch = useDispatch();
9 | const { bookmarkedBlogs: blogsData } = useSelector((store) => store.blog)
10 |
11 | useEffect(() => {
12 | dispatch(getBookmarkedBlogs());
13 | },[])
14 |
15 | const handleShowMoreButton = () => {
16 | dispatch(getBookmarkedBlogs(blogsData.currentPage + 1))
17 | }
18 |
19 | return (
20 |
21 |
Reading List
22 |
23 |
24 |
25 |
26 |
27 | )
28 | }
29 |
30 | export default Bookmarks;
31 |
--------------------------------------------------------------------------------
/client/src/pages/EditBlog.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react'
2 | import { useDispatch, useSelector } from 'react-redux'
3 | import { useParams } from 'react-router-dom'
4 | import BlogForm from '../components/BlogForm'
5 | import { getBlogDetails } from '../redux/slices/blogsSlice'
6 |
7 | const EditBlog = () => {
8 |
9 | const dispatch = useDispatch();
10 | const params = useParams();
11 | const { blogDetails } = useSelector((store) => store.blog);
12 | const { _id, title, content, createdAt, author, tags } = blogDetails;
13 |
14 | useEffect(() => {
15 | dispatch(getBlogDetails(params.id));
16 | },[params.id])
17 |
18 | return (
19 |
20 |
21 |
22 | )
23 | }
24 |
25 | export default EditBlog
--------------------------------------------------------------------------------
/client/src/pages/LandingPage.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react'
2 | import { useDispatch, useSelector } from 'react-redux';
3 | import BlogsList from '../components/Blogs/BlogsList';
4 | import TagsList from '../components/TagsList';
5 | import { getAllBlogs } from '../redux/slices/blogsSlice';
6 |
7 | const LandingPage = () => {
8 |
9 | const dispatch = useDispatch();
10 | const { blogsData } = useSelector((store) => store.blog);
11 |
12 | const handleShowMoreButton = () => {
13 | dispatch(getAllBlogs(blogsData.currentPage + 1))
14 | }
15 |
16 | return (
17 |
23 | )
24 | }
25 |
26 | export default LandingPage;
27 |
--------------------------------------------------------------------------------
/client/src/pages/LoginPage.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react'
2 | import { useNavigate } from 'react-router-dom';
3 | import LoginForm from '../components/UserForm/LoginForm'
4 | import SignUpForm from '../components/UserForm/SignUpForm';
5 |
6 | const LoginPage = () => {
7 |
8 | const [isSignup, setIsSignup] = useState(false);
9 | const navigate = useNavigate();
10 |
11 | const handleSignInButton = (e) => {
12 | e.stopPropagation();
13 | setIsSignup(false)
14 | }
15 |
16 | const handleSignUpButton = (e) => {
17 | e.stopPropagation();
18 | setIsSignup(true)
19 | }
20 |
21 | const gotoIndexPage = () => {
22 | navigate("/");
23 | }
24 |
25 | return (
26 | <>
27 |
28 |
29 | {
30 | isSignup
31 | ?
32 |
33 | :
34 |
35 | }
36 |
37 |
38 | >
39 | )
40 | }
41 |
42 | export default LoginPage;
43 |
--------------------------------------------------------------------------------
/client/src/pages/SearchedBlogs.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react'
2 | import { useDispatch, useSelector } from 'react-redux'
3 | import { useParams, useSearchParams } from 'react-router-dom'
4 | import BlogsList from '../components/Blogs/BlogsList'
5 | import { getBlogsByTopic, getSearchedBlogs } from '../redux/slices/blogsSlice'
6 |
7 | const SearchedBlogs = ({type}) => {
8 |
9 | const dispatch = useDispatch();
10 | const params = useParams();
11 |
12 | const [searchParams, setSearchParams] = useSearchParams();
13 | const { searchedBlogs: blogsData } = useSelector((store) => store.blog);
14 |
15 | const searchInput = searchParams.get('search');
16 |
17 | useEffect(() => {
18 | if(type === "search"){
19 | dispatch(getSearchedBlogs({searchInput}));
20 | }
21 | else{
22 | dispatch(getBlogsByTopic({topicName: params.name}));
23 | }
24 | },[searchParams, params.name, type])
25 |
26 | const handleShowMoreButton = () => {
27 | if(type === "search"){
28 | dispatch(getSearchedBlogs({searchInput, page:blogsData.currentPage + 1}));
29 | }
30 | else{
31 | dispatch(getBlogsByTopic({topicName: params.name, page:blogsData.currentPage + 1}));
32 | }
33 | }
34 |
35 | return (
36 |
37 |
Results for {params.name} {searchInput}
38 |
39 |
40 |
41 |
42 |
43 | )
44 | }
45 |
46 | export default SearchedBlogs;
47 |
--------------------------------------------------------------------------------
/client/src/pages/UserPublishedBlogsList.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react'
2 | import { BiPlus } from 'react-icons/bi'
3 | import { useDispatch, useSelector } from 'react-redux'
4 | import { Link, useParams } from 'react-router-dom'
5 | import BlogsList from '../components/Blogs/BlogsList'
6 | import { getBlogsByAuthor } from '../redux/slices/blogsSlice'
7 |
8 | const UserPublishedBlogsList = ({ type }) => {
9 | const params = useParams();
10 | const dispatch = useDispatch();
11 |
12 | const { userPublishedBlogs: blogsData } = useSelector((store) => store.blog);
13 | const { _id } = useSelector((store) => store.auth.userData);
14 |
15 | useEffect(() => {
16 | if (type === 1) {
17 | dispatch(getBlogsByAuthor({ userId: _id }));
18 | }
19 | else {
20 | dispatch(getBlogsByAuthor({ userId: params.id }));
21 | }
22 | }, [params.id, type, _id]);
23 |
24 | const handleShowMoreButton = () => {
25 | if (type === 1) {
26 | dispatch(getBlogsByAuthor({ userId: _id, page: blogsData?.currentPage + 1 }));
27 | }
28 | else {
29 | dispatch(getBlogsByAuthor({ userId: params.id, page: blogsData?.currentPage + 1 }));
30 | }
31 | }
32 |
33 | return (
34 |
35 |
36 |
37 | {
38 | type === 1 ? (
39 | 'My Blogs'
40 | ) : (
41 | `${blogsData?.blogsList?.[0]?.author?.name || 'User'}'s Blogs`
42 | )
43 | }
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | )
52 | }
53 |
54 | export default UserPublishedBlogsList
--------------------------------------------------------------------------------
/client/src/pages/index.js:
--------------------------------------------------------------------------------
1 | import React, { lazy, Suspense } from 'react'
2 | import { Route, Routes } from 'react-router-dom';
3 | import LayoutWithNavbar from '../components/Layout/WithNavbar';
4 | import LayoutWithoutNavbar from '../components/Layout/WithoutNavbar';
5 | import LoadingComponent from '../components/LoadingComponent';
6 | import { AnonymousRoute } from '../components/Routes/AnonymousRoute';
7 | import { PrivateRoute } from '../components/Routes/PrivateRoute';
8 |
9 | const AddBlog = lazy(() => import("./AddBlog"));
10 | const Blog = lazy(() => import("./Blog"));
11 | const Bookmarks = lazy(() => import("./Bookmarks"));
12 | const EditBlog = lazy(() => import("./EditBlog"));
13 | const LandingPage = lazy(() => import("./LandingPage"));
14 | const LoginPage = lazy(() => import("./LoginPage"));
15 | const SearchedBlogs = lazy(() => import("./SearchedBlogs"));
16 | const UserPublishedBlogsList = lazy(() => import("./UserPublishedBlogsList"));
17 |
18 | const Pages = () => {
19 | return (
20 | }>
21 |
22 | }>
23 | } />
24 | } />
25 | } />
26 | } />
27 | } />
28 | } />
29 | } />
30 |
31 | }>
32 | } />
33 | } />
34 | } />
35 | {/*
36 | */}
37 |
38 |
39 |
40 | )
41 | }
42 |
43 | export default Pages;
44 |
--------------------------------------------------------------------------------
/client/src/redux/Api.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 |
3 | const axiosInstance = axios.create({
4 | baseURL: process.env.REACT_APP_BASE_URL,
5 | withCredentials: true,
6 | })
7 |
8 | export default axiosInstance;
9 |
10 |
--------------------------------------------------------------------------------
/client/src/redux/checkForTokenExpiry.js:
--------------------------------------------------------------------------------
1 | import axiosInstance from "./Api";
2 | import { removeUser } from "./slices/authSlice";
3 |
4 | export const checkTokenExpirationMiddleware= ({ dispatch, getState }) => (next) => async (action) => {
5 | try {
6 | const response = await axiosInstance.get("/auth/checkstatus");
7 | console.log(response);
8 | if (response.status === 401 || response.status === 400){
9 | localStorage.removeItem("blog-user");
10 | dispatch(removeUser());
11 | next(action);
12 | }
13 | else{
14 | next(action);
15 | }
16 | } catch (error) {
17 | console.log(error);
18 | next(action);
19 | }
20 | };
--------------------------------------------------------------------------------
/client/src/redux/slices/authSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from '@reduxjs/toolkit';
2 | import axios from '../Api';
3 | import { toast } from "react-toastify";
4 | import 'react-toastify/dist/ReactToastify.css';
5 |
6 | const initialState = {
7 | loading: false,
8 | error: { isError: false, message: '' },
9 | userData: {},
10 | };
11 |
12 | export const authSlice = createSlice({
13 | name: "auth",
14 | initialState,
15 | reducers: {
16 | setLoading: (state) => {
17 | state.loading = true;
18 | },
19 | getUserSuccess: (state, { payload }) => {
20 | state.loading = false;
21 | state.userData = { ...payload };
22 | state.error = { isError: false, message: '' };
23 | },
24 | getUserFailure: (state, { payload }) => {
25 | state.loading = false;
26 | state.userData = {};
27 | state.error = { isError: true, message: payload };
28 | },
29 | removeUser: (state) => {
30 | state.userData = {}
31 | }
32 | },
33 | });
34 |
35 | export const { setLoading, getUserSuccess, getUserFailure, removeUser } = authSlice.actions;
36 |
37 | export default authSlice.reducer;
38 |
39 | export const signUpUserWithGoogle = (data, callback) => async (dispatch) => {
40 | try {
41 | dispatch(setLoading(true));
42 | const response = await axios.post('/auth/google', {
43 | data,
44 | });
45 | if (response.status === 201 || response.status === 200) {
46 | dispatch(getUserSuccess(response.data.result));
47 | localStorage.setItem("blog-user", JSON.stringify(response.data.result));
48 | toast.success('Sign up successfully done');
49 |
50 | if (callback) {
51 | callback();
52 | }
53 | }
54 | } catch (error) {
55 | console.log(error.response.data.message);
56 | toast.error(error.response.data.message);
57 | dispatch(getUserFailure(error.response.data.message))
58 | }
59 | }
60 |
61 | export const signUpUser = (data, callback) => async (dispatch) => {
62 | const { firstName, lastName, email, password, confirmPassword } = data;
63 | try {
64 | dispatch(setLoading(true));
65 |
66 | const response = await axios.post(
67 | "/auth/signup",
68 | { firstName, lastName, email, password, confirmPassword },
69 | );
70 | if (response.status === 201) {
71 | dispatch(getUserSuccess(response.data.result));
72 | localStorage.setItem("blog-user", JSON.stringify(response.data.result));
73 | toast.success('Sign up successfully done');
74 | if (callback) {
75 | callback();
76 | }
77 | }
78 | } catch (error) {
79 | console.log(error.response.data.message);
80 | toast.error(error.response.data.message);
81 | dispatch(getUserFailure(error.response.data.message))
82 | }
83 | };
84 |
85 | export const loginUser = (data, callback) => async (dispatch) => {
86 | const { email, password } = data;
87 | try {
88 | dispatch(setLoading(true));
89 |
90 | const response = await axios.post(
91 | "/auth/signin",
92 | { email, password },
93 | );
94 | if (response.status === 200) {
95 | dispatch(getUserSuccess(response.data.result));
96 | localStorage.setItem("blog-user", JSON.stringify(response.data.result));
97 | toast.success("Logged in successfully")
98 | if (callback) {
99 | callback();
100 | }
101 | }
102 | } catch (error) {
103 | console.log(error.response.data.message);
104 | toast.error(error.response.data.message);
105 | dispatch(getUserFailure(error.response.data.message))
106 | }
107 | };
108 |
109 | // export const setUserData = (user) => dispatch => {
110 | // localStorage.setItem("blog-user", JSON.stringify(user));
111 | // dispatch(setUser(user));
112 | // }
113 |
114 | export const getUserData = () => dispatch => {
115 | const userData = localStorage.getItem("blog-user");
116 | dispatch(getUserSuccess(JSON.parse(userData)));
117 | }
118 |
119 | export const removeUserData = (callback) => async dispatch => {
120 | try {
121 | const response = await axios.get("/auth/signout");
122 | console.log(response);
123 | localStorage.removeItem("blog-user");
124 | dispatch(removeUser());
125 | if (callback) {
126 | callback();
127 | }
128 |
129 | } catch (error) {
130 | console.log(error.response.data.message);
131 | toast.error(error.response.data.message);
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/client/src/redux/slices/blogsSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from '@reduxjs/toolkit';
2 | import axios from '../Api';
3 | import { removeUserData } from './authSlice';
4 | import { toast } from "react-toastify";
5 | import 'react-toastify/dist/ReactToastify.css';
6 |
7 | const initialState = {
8 | loading: false,
9 | error: { isError: false, message: '' },
10 | blogsData: {
11 | totalPages: 0,
12 | currentPage: 0,
13 | blogsList: [],
14 | },
15 | blogDetails: {},
16 | blogLikesNumber: [],
17 | blogCommentsList: [],
18 | bookmarkedBlogsId: [],
19 | bookmarkedBlogs: {
20 | totalPages: 0,
21 | currentPage: 0,
22 | blogsList: [],
23 | },
24 | searchedBlogs: {
25 | totalPages: 0,
26 | currentPage: 0,
27 | blogsList: [],
28 | },
29 | userPublishedBlogs: {
30 | totalPages: 0,
31 | currentPage: 0,
32 | blogsList: [],
33 | },
34 | mostPopularTopics: [],
35 | };
36 |
37 | const blogsSlice = createSlice({
38 | name: 'blog',
39 | initialState,
40 | reducers: {
41 | setLoading: (state, { payload }) => {
42 | state.loading = payload;
43 | },
44 | addBlogSuccess: (state, { payload }) => {
45 | state.loading = false;
46 | state.error = { isError: false, message: '' };
47 | },
48 | addBlogFailure: (state, { payload }) => {
49 | state.loading = false;
50 | state.error = { isError: true, message: payload };
51 | },
52 | setBlogsSuccess: (state, { payload }) => {
53 | state.loading = false;
54 | state.blogsData.blogsList = [...state.blogsData.blogsList, ...payload.data];
55 | state.blogsData.totalPages = payload.numberOfPages;
56 | state.blogsData.currentPage = payload.current;
57 | state.error = { isError: false, message: '' };
58 | },
59 | setBlogsFailure: (state, { payload }) => {
60 | state.loading = false;
61 | state.error = { isError: true, message: payload };
62 | },
63 | resetBlogs: (state, { payload }) => {
64 | state.blogsData.blogsList = [];
65 | state.blogsData.totalPages = 0;
66 | state.blogsData.currentPage = 0;
67 | },
68 | setBlogDetailsSuccess: (state, { payload }) => {
69 | state.loading = false;
70 | state.blogDetails = payload;
71 | state.error = { isError: false, message: '' };
72 | },
73 | setBlogDetailsFailure: (state, { payload }) => {
74 | state.loading = false;
75 | state.error = { isError: true, message: payload };
76 | },
77 | resetBlogDetails: (state, { payload }) => {
78 | state.blogDetails = {};
79 | state.blogDetails = [];
80 | },
81 | setBlogLikesNumber: (state, { payload }) => {
82 | state.blogLikesNumber = payload;
83 | },
84 | setBlogComments: (state, { payload }) => {
85 | state.blogCommentsList = payload;
86 | },
87 | setMostPopularTopicsSuccess: (state, { payload }) => {
88 | state.mostPopularTopics = payload.data;
89 | state.error = { isError: false, message: '' };
90 | },
91 | setMostPopularTopicsFailure: (state, { payload }) => {
92 | state.error = { isError: true, message: payload };
93 | },
94 | setBookmarkedBlogSuccess: (state, { payload }) => {
95 | state.loading = false;
96 | state.bookmarkedBlogs.blogsList = [...state.bookmarkedBlogs.blogsList, ...payload.data];
97 | state.bookmarkedBlogs.totalPages = payload.numberOfPages;
98 | state.bookmarkedBlogs.currentPage = payload.current;
99 | state.error = { isError: false, message: '' };
100 | },
101 | setBookmarkedBlogFailure: (state, { payload }) => {
102 | state.loading = false;
103 | state.error = { isError: true, message: payload };
104 | },
105 | resetBookmarkedBlogs: (state, { payload }) => {
106 | state.bookmarkedBlogs.blogsList = [];
107 | state.bookmarkedBlogs.totalPages = 0;
108 | state.bookmarkedBlogs.currentPage = 0;
109 | },
110 | setBookmarkedBlogIdSuccess: (state, { payload }) => {
111 | state.bookmarkedBlogsId = payload;
112 | state.error = { isError: false, message: '' };
113 | },
114 | setBookmarkedBlogIdFailure: (state, { payload }) => {
115 | state.error = { isError: true, message: payload };
116 | },
117 | resetBookmarkedBlogsId: (state, { payload }) => {
118 | state.bookmarkedBlogsId = [];
119 | state.error = { isError: false, message: '' };
120 | },
121 | setSearchedBlogsSuccess: (state, { payload }) => {
122 | state.loading = false;
123 | state.searchedBlogs.blogsList = [...state.searchedBlogs.blogsList, ...payload.data];
124 | state.searchedBlogs.totalPages = payload.numberOfPages;
125 | state.searchedBlogs.currentPage = payload.current;
126 | state.error = { isError: false, message: '' };
127 | },
128 | setSearchedBlogsFailure: (state, { payload }) => {
129 | state.loading = false;
130 | state.error = { isError: true, message: payload };
131 | },
132 | resetSearchedBlogs: (state, { payload }) => {
133 | state.searchedBlogs.blogsList = [];
134 | state.searchedBlogs.totalPages = 0;
135 | state.searchedBlogs.currentPage = 0;
136 | },
137 | setUserPublishedBlogsSuccess: (state, { payload }) => {
138 | state.loading = false;
139 | state.userPublishedBlogs.blogsList = [...state.userPublishedBlogs.blogsList, ...payload.data];
140 | state.userPublishedBlogs.totalPages = payload.numberOfPages;
141 | state.userPublishedBlogs.currentPage = payload.current;
142 | state.error = { isError: false, message: '' };
143 | },
144 | setUserPublishedBlogsFailure: (state, { payload }) => {
145 | state.loading = false;
146 | state.error = { isError: true, message: payload };
147 | },
148 | resetUserPublishedBlogs: (state, { payload }) => {
149 | state.userPublishedBlogs.blogsList = [];
150 | state.userPublishedBlogs.totalPages = 0;
151 | state.userPublishedBlogs.currentPage = 0;
152 | },
153 | },
154 | });
155 |
156 | export const { setLoading, addBlogSuccess, addBlogFailure, setBlogsSuccess, setBlogsFailure, resetBlogs, setBlogDetailsSuccess, setBlogDetailsFailure, resetBlogDetails, setBlogLikesNumber, setBlogComments, setMostPopularTopicsSuccess, setMostPopularTopicsFailure, setBookmarkedBlogSuccess, setBookmarkedBlogFailure, resetBookmarkedBlogs, resetBookmarkedBlogsId, setBookmarkedBlogIdSuccess, setBookmarkedBlogIdFailure, setSearchedBlogsSuccess, setSearchedBlogsFailure, resetSearchedBlogs, setUserPublishedBlogsSuccess, setUserPublishedBlogsFailure, resetUserPublishedBlogs } = blogsSlice.actions;
157 |
158 | export default blogsSlice.reducer;
159 |
160 | export const getAllBlogs = (page) => async (dispatch) => {
161 | if (page === undefined) {
162 | dispatch(resetBlogs());
163 | }
164 | try {
165 | dispatch(setLoading(true));
166 | const response = await axios.get(`/blog`, {
167 | params: {
168 | page,
169 | }
170 | });
171 | dispatch(setBlogsSuccess(response.data));
172 | } catch (error) {
173 | console.log(error.message);
174 | toast.error(error.response.data.message);
175 | dispatch(setBlogsFailure(error.response.data.message))
176 | }
177 | }
178 |
179 | export const getBlogDetails = (blogId) => async (dispatch) => {
180 | try {
181 | dispatch(setLoading(true));
182 | const response = await axios.get(`/blog/${blogId}`);
183 | dispatch(setBlogDetailsSuccess(response.data));
184 | dispatch(setBlogLikesNumber(response.data.likes));
185 | dispatch(setBlogComments(response.data.comments));
186 | } catch (error) {
187 | console.log(error.message);
188 | if (error.response.status === 404) {
189 | toast.error(error.message);
190 | }
191 | else {
192 | toast.error(error.response.data.message);
193 | }
194 | dispatch(setBlogDetailsFailure(error.response.data.message))
195 | }
196 | }
197 |
198 | export const getBlogsByAuthor = ({ userId, page }) => async (dispatch) => {
199 | if (page === undefined) {
200 | dispatch(resetUserPublishedBlogs());
201 | }
202 | try {
203 | dispatch(setLoading(true));
204 | const response = await axios.get(`/blog/author/${userId}`, {
205 | params: {
206 | page,
207 | }
208 | });
209 | dispatch(setUserPublishedBlogsSuccess(response.data));
210 | } catch (error) {
211 | if (error.response.status === 401) {
212 | dispatch(removeUserData());
213 | }
214 | if (error.response.status === 404) {
215 | toast.error(error.message);
216 | }
217 | else {
218 | toast.error(error.response.data.message);
219 | }
220 | dispatch(setUserPublishedBlogsFailure(error.response.data.message))
221 | }
222 | }
223 |
224 | export const getSearchedBlogs = ({ searchInput, page }) => async (dispatch) => {
225 | if (page === undefined) {
226 | dispatch(resetSearchedBlogs());
227 | }
228 | try {
229 | dispatch(setLoading(true));
230 | const response = await axios.get(`/blog/search?`, {
231 | params: {
232 | search: searchInput,
233 | page,
234 | },
235 | });
236 | dispatch(setSearchedBlogsSuccess(response.data));
237 | } catch (error) {
238 | console.log(error);
239 | if (error.response.status === 401) {
240 | dispatch(removeUserData());
241 | }
242 | toast.error(error.response.data.message);
243 | dispatch(setSearchedBlogsFailure(error.response.data.message))
244 | }
245 | }
246 |
247 | export const getBlogsByTopic = ({ topicName, page }) => async (dispatch) => {
248 |
249 | if (page === undefined) {
250 | dispatch(resetSearchedBlogs());
251 | }
252 |
253 | try {
254 | dispatch(setLoading(true));
255 | const response = await axios.get(`/blog/topic/${topicName}`, {
256 | params: {
257 | page,
258 | }
259 | });
260 | dispatch(setSearchedBlogsSuccess(response.data));
261 |
262 | } catch (error) {
263 | console.log(error);
264 | if (error.response.status === 401) {
265 | dispatch(removeUserData());
266 | }
267 | toast.error(error.response.data.message);
268 | dispatch(setSearchedBlogsFailure(error.response.data.message))
269 | }
270 | }
271 |
272 | export const createNewBlog = (blogData, callback) => async (dispatch) => {
273 | const { title, content, tagList } = blogData;
274 | dispatch(setLoading(true));
275 | try {
276 | const response = await axios.post(`/blog`, {
277 | title,
278 | content,
279 | tags: tagList,
280 | });
281 | if (callback) {
282 | callback();
283 | }
284 | toast.success('New Blog Created Successfully');
285 |
286 | } catch (error) {
287 | console.log(error)
288 | if (error.response.status === 401) {
289 | dispatch(removeUserData());
290 | }
291 | toast.error(error.response.data.message);
292 | dispatch(addBlogFailure(error.response.data.message))
293 | }
294 | }
295 |
296 | export const BookmarkBlog = (id) => async (dispatch) => {
297 | try {
298 | const response = await axios.post(`/blog/bookmarks/${id}`);
299 | dispatch(setBookmarkedBlogIdSuccess(response.data.data));
300 | toast.success(response.data.message);
301 |
302 | } catch (error) {
303 | console.log(error);
304 | if (error.response.status === 401) {
305 | dispatch(removeUserData());
306 | }
307 | toast.error(error.response.data.message);
308 | dispatch(setBookmarkedBlogIdFailure(error.response.data.message))
309 | }
310 | }
311 |
312 | export const getBookmarkedBlogsId = () => async (dispatch) => {
313 | try {
314 | const response = await axios.get(`/blog/bookmarksId`);
315 | dispatch(setBookmarkedBlogIdSuccess(response.data.data));
316 |
317 | } catch (error) {
318 | console.log(error);
319 | if (error.response.status === 401) {
320 | dispatch(removeUserData());
321 | dispatch(resetBookmarkedBlogsId());
322 | }
323 | dispatch(setBookmarkedBlogIdFailure(error.response.data.message));
324 | }
325 | }
326 |
327 | export const getBookmarkedBlogs = (page) => async (dispatch) => {
328 | if (page === undefined) {
329 | dispatch(resetBookmarkedBlogs());
330 | }
331 | dispatch(setLoading(true));
332 | try {
333 | const response = await axios.get(`/blog/bookmarks`, {
334 | params: {
335 | page,
336 | }
337 | });
338 |
339 | dispatch(setBookmarkedBlogSuccess(response.data))
340 | } catch (error) {
341 | console.log(error)
342 | if (error.response.status === 401) {
343 | dispatch(removeUserData());
344 | }
345 | dispatch(setBookmarkedBlogFailure(error.response.data.message))
346 | }
347 | }
348 |
349 | export const getPopularTopicsList = () => async (dispatch) => {
350 | try {
351 | const response = await axios.get(`/blog/topics`);
352 |
353 | dispatch(setMostPopularTopicsSuccess(response.data));
354 | } catch (error) {
355 | console.log(error);
356 | toast.error(error.response.data.message);
357 | dispatch(setMostPopularTopicsFailure(error.response.data.message))
358 | }
359 | }
360 |
361 | export const updateBlog = (blogData, callback) => async (dispatch) => {
362 | const { blogId } = blogData;
363 | dispatch(setLoading(true));
364 | try {
365 | const response = await axios.patch(`/blog/${blogId}`, blogData);
366 | if (callback) {
367 | callback();
368 | }
369 | toast.success('Blog updated');
370 |
371 | } catch (error) {
372 | console.log(error)
373 | if (error.response.status === 401) {
374 | dispatch(removeUserData());
375 | }
376 | toast.error(error.response.data.message);
377 | dispatch(addBlogFailure(error.response.data.message));
378 | }
379 | }
380 |
381 | export const deleteBlog = (_id, callback) => async (dispatch) => {
382 | try {
383 | dispatch(setLoading(true));
384 | const response = await axios.delete(`/blog/${_id}`);
385 | if (callback) {
386 | callback();
387 | }
388 | toast.success('Blog Deleted Successfully');
389 |
390 | } catch (error) {
391 | console.log(error);
392 | if (error.response.status === 401) {
393 | dispatch(removeUserData());
394 | }
395 | toast.error(error.response.data.message);
396 | dispatch(addBlogFailure(error.response.data.message));
397 | }
398 | }
399 |
400 | export const likeBlog = (_id) => async (dispatch) => {
401 | try {
402 | const response = await axios.get(`/blog/likeBlog/${_id}`);
403 | dispatch(setBlogLikesNumber(response.data.data));
404 | toast.success(response.data.message);
405 |
406 | } catch (error) {
407 | console.log(error);
408 | if (error.response.status === 401) {
409 | dispatch(removeUserData());
410 | }
411 | toast.error(error.response.data.message);
412 | }
413 | }
414 |
415 | export const postBlogComment = ({ blogId, comment }) => async (dispatch) => {
416 | try {
417 | const response = await axios.post(`/blog/commentBlog/${blogId}`, {
418 | comment: comment,
419 | });
420 | dispatch(setBlogComments(response.data.data));
421 | } catch (error) {
422 | console.log(error);
423 | if (error.response.status === 401) {
424 | dispatch(removeUserData());
425 | }
426 | toast.error(error.response.data.message);
427 | }
428 | }
429 |
--------------------------------------------------------------------------------
/client/src/redux/slices/userSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from '@reduxjs/toolkit';
2 |
3 | const initialState = {
4 | id: null,
5 | firstName: null,
6 | lastName: null,
7 | email: null,
8 | imageUrl: null,
9 | }
10 |
11 | const userSlice = createSlice({
12 | name: 'user',
13 | initialState,
14 | reducers:{
15 | setUser: (state, { payload }) => {
16 | state.id = payload.id;
17 | state.firstName = payload.firstName;
18 | state.lastName = payload.lastName;
19 | state.email = payload.email;
20 | state.imageUrl = payload.imageUrl;
21 | },
22 | removeUser: (state) => {
23 | state.id = null;
24 | state.firstName = null;
25 | state.lastName = null;
26 | state.email = null;
27 | state.imageUrl = null;
28 | },
29 | }
30 | });
31 |
32 | export const { setUser, removeUser } = userSlice.actions;
33 |
34 | export default userSlice.reducer;
35 |
36 | export const setUserData = (user) => dispatch => {
37 | localStorage.setItem("blog-user", JSON.stringify(user));
38 | dispatch(setUser(user));
39 | }
40 |
41 | export const getUserData = () => dispatch => {
42 | const userData = localStorage.getItem("blog-user");
43 | dispatch(setUser(JSON.parse(userData)));
44 | }
45 |
46 | export const removeUserData = () => dispatch => {
47 | localStorage.removeItem("blog-user");
48 | dispatch(removeUser());
49 | }
50 |
--------------------------------------------------------------------------------
/client/src/redux/store.js:
--------------------------------------------------------------------------------
1 | import { configureStore } from '@reduxjs/toolkit';
2 | import userReducer from './slices/userSlice';
3 | import authReducer from './slices/authSlice';
4 | import { checkTokenExpirationMiddleware } from './checkForTokenExpiry';
5 | import blogReducer from './slices/blogsSlice';
6 |
7 | export const store = configureStore({
8 | reducer: {
9 | user: userReducer,
10 | auth: authReducer,
11 | blog: blogReducer,
12 | },
13 | // middleware: getDefaultMiddleware => getDefaultMiddleware().concat(checkTokenExpirationMiddleware),
14 | });
15 |
--------------------------------------------------------------------------------
/client/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | content: ["./src/**/*.{html,js}"],
4 | theme: {
5 | extend: {},
6 | },
7 | plugins: [],
8 | }
9 |
10 |
--------------------------------------------------------------------------------
/server/controllers/blogs.js:
--------------------------------------------------------------------------------
1 | import express from 'express';
2 | import mongoose from 'mongoose';
3 |
4 | import Blog from '../models/Blog.js';
5 | import User from '../models/User.js';
6 |
7 | const router = express.Router();
8 |
9 | export const getBlogs = async (req, res) => {
10 | const currentPage = Number(req.query.page) || 1;
11 | const LIMIT = 10;
12 | const skipPosts = (currentPage - 1) * LIMIT;
13 | try {
14 | const total = await Blog.countDocuments();
15 | const posts = await Blog.find().populate("author").sort({ createdAt: -1 }).limit(LIMIT).skip(skipPosts);
16 | res.status(200).json({ data: posts, current: Number(currentPage), numberOfPages: Math.ceil(total / LIMIT) });
17 |
18 | } catch (error) {
19 | res.status(404).json({ message: error.message });
20 | }
21 | }
22 |
23 | export const getBlogDetails = async (req, res) => {
24 | const { id } = req.params;
25 | try {
26 | const post = await Blog.findById(id).populate("author").populate({
27 | path: 'comments',
28 | populate: {
29 | path: 'user',
30 | select: { 'imgUrl': 1, 'name': 1, '_id': 1, 'email': 1 }
31 | },
32 | });
33 | res.status(200).json(post);
34 | } catch (error) {
35 | res.status(404).json({ message: error.message });
36 | }
37 | }
38 |
39 | export const createBlog = async (req, res) => {
40 | const blogData = req.body;
41 |
42 | try {
43 | const response = await Blog.create({ ...blogData, author: req.user._id });
44 | res.status(201).json(response);
45 | } catch (error) {
46 | console.log(error);
47 | res.status(409).json({ message: error.message });
48 | }
49 | }
50 |
51 | export const getPopularTags = async (req, res) => {
52 |
53 | try {
54 | const response = await Blog.aggregate([{ $unwind: "$tags" }, {
55 | $group: { /* execute 'grouping' */
56 | _id: '$tags', /* using the 'token' value as the _id */
57 | count: { $sum: 1 } /* create a sum value */
58 | }
59 | },
60 | { "$sort": { "count": -1 } }
61 | ]).limit(15);
62 | return res.status(200).json({ data: response, success: true });
63 | } catch (error) {
64 | res.status(404).json({ message: error.message });
65 | }
66 | }
67 |
68 | export const updateBlog = async (req, res) => {
69 | const { id } = req.params;
70 | const blogData = req.body;
71 | if (req.user._id.toString() !== blogData.authorId) {
72 | return res.status(403).json({ message: 'User is not authorized to update the data' });
73 | }
74 | else {
75 | try {
76 | const response = await Blog.findByIdAndUpdate(id, blogData, { new: true });
77 | res.status(201).json(response);
78 | } catch (error) {
79 | res.status(404).json({ message: error.message });
80 | }
81 | }
82 | }
83 |
84 | export const deleteBlog = async (req, res) => {
85 | const { id } = req.params;
86 |
87 | try {
88 | const response = await Blog.findByIdAndRemove(id);
89 | res.status(200).json(response);
90 | } catch (error) {
91 | res.status(404).json({ message: error.message });
92 | }
93 | }
94 |
95 | export const getBlogsByAuthor = async (req, res) => {
96 | const { id } = req.params;
97 | const currentPage = Number(req.query.page) || 1;
98 | const LIMIT = 10;
99 | const skipBlogs = (currentPage - 1) * LIMIT;
100 | try {
101 | const total = await Blog.countDocuments({ author: id });
102 | const blogs = await Blog.find({ author: id }).populate("author").sort({ createdAt: -1 }).limit(LIMIT).skip(skipBlogs);
103 |
104 | res.status(200).json({ data: blogs, current: Number(currentPage), numberOfPages: Math.ceil(total / LIMIT) });
105 |
106 | } catch (error) {
107 | res.status(404).json({ message: error.message });
108 | }
109 | }
110 |
111 | export const getBlogsBySearch = async (req, res) => {
112 | let { search } = req.query;
113 |
114 | try {
115 | const searchInput = new RegExp(search, "i");
116 | // const blogs = await Blog.find({ $text: {$search: search}}, {tags: tag}).populate("author");
117 | const blogs = await Blog.find({ $or: [{ 'title': { "$regex": searchInput } }, { 'content': { "$regex": searchInput } }, { 'tags': { "$regex": searchInput } }] }).populate("author").sort({ createdAt: -1 });
118 |
119 | res.status(200).json({ data: blogs });
120 |
121 | } catch (error) {
122 | res.status(404).json({ message: error.message });
123 | }
124 | }
125 |
126 | export const getBlogsByTopic = async (req, res) => {
127 | const { name } = req.params;
128 | const currentPage = Number(req.query.page) || 1;
129 | const LIMIT = 10;
130 | const skipBlogs = (currentPage - 1) * LIMIT;
131 | try {
132 | const total = await Blog.countDocuments({
133 | tags: {
134 | $elemMatch: {
135 | $regex: name,
136 | $options: 'i'
137 | }
138 | }
139 | });
140 | const blogs = await Blog.find({
141 | tags: {
142 | $elemMatch: {
143 | $regex: name,
144 | $options: 'i'
145 | }
146 | }
147 | }).populate("author").sort({ createdAt: -1 }).limit(LIMIT).skip(skipBlogs);
148 |
149 | res.status(200).json({ data: blogs, current: Number(currentPage), numberOfPages: Math.ceil(total / LIMIT) });
150 |
151 | } catch (error) {
152 | res.status(404).json({ message: error.message });
153 | }
154 | }
155 |
156 | export const saveUnsaveBlog = async (req, res) => {
157 |
158 | try {
159 | const user = await User.findById(req.user._id)
160 | const post = await Blog.findById(req.params.id);
161 |
162 | if (user.blogsSaved.includes(post._id.toString())) {
163 | user.blogsSaved = user.blogsSaved.filter((p) => p.toString() !== post._id.toString());
164 | await user.save();
165 |
166 | return res.status(200).json({ data: user.blogsSaved, success: true, message: "Blog Unsaved" });
167 | }
168 | else {
169 | user.blogsSaved.push(post._id);
170 | await user.save();
171 |
172 | return res.status(200).json({ data: user.blogsSaved, success: true, message: "Blog Saved" });
173 | }
174 | } catch (error) {
175 | res.status(404).json({ message: error.message });
176 | }
177 | }
178 |
179 | export const likeUnlikeBlog = async (req, res) => {
180 |
181 | try {
182 | const blog = await Blog.findById(req.params.id);
183 |
184 | if (blog.likes.includes(req.user._id)) {
185 | const index = blog.likes.indexOf(req.user._id);
186 |
187 | blog.likes.splice(index, 1);
188 | await blog.save();
189 |
190 | return res.status(200).json({ data: blog.likes, success: true, message: "Blog Unliked" });
191 | }
192 | else {
193 | blog.likes.push(req.user._id);
194 | await blog.save();
195 |
196 | return res.status(200).json({ data: blog.likes, success: true, message: "Blog Liked" });
197 | }
198 | } catch (error) {
199 | res.status(404).json({ message: error.message });
200 | }
201 | }
202 |
203 | export const commentBlog = async (req, res) => {
204 | try {
205 | const blog = await Blog.findById(req.params.id);
206 |
207 | blog.comments.push({
208 | user: req.user._id,
209 | comment: req.body.comment
210 | });
211 |
212 | await blog.save();
213 | await blog.populate({
214 | path: 'comments',
215 | populate: {
216 | path: 'user',
217 | select: { 'imgUrl': 1, 'name': 1, '_id': 1, 'email': 1 }
218 | },
219 | });
220 | return res.status(200).json({ data: blog.comments, success: true })
221 |
222 | } catch (error) {
223 | res.status(404).json({ message: error.message });
224 | }
225 | }
226 |
227 | export const getBookmarkedBlogs = async (req, res) => {
228 |
229 | const currentPage = Number(req.query.page) || 1;
230 | const LIMIT = 10;
231 | const skipPosts = (currentPage - 1) * LIMIT;
232 |
233 | try {
234 | const user = await User.findById(req.user._id);
235 | const total = await Blog.countDocuments({ '_id': { $in: user.blogsSaved } });
236 | const blogs = await Blog.find({ '_id': { $in: user.blogsSaved } }).populate("author").sort({ createdAt: -1 }).limit(LIMIT).skip(skipPosts);
237 |
238 | res.status(200).json({ data: blogs, current: Number(currentPage), numberOfPages: Math.ceil(total / LIMIT) });
239 | } catch (error) {
240 | res.status(404).json({ message: error.message });
241 | }
242 | }
243 |
244 | export const getBookmarkedBlogsId = async (req, res) => {
245 |
246 | try {
247 | const user = await User.findById(req.user._id);
248 |
249 | res.status(200).json({ data: user.blogsSaved });
250 |
251 | } catch (error) {
252 | res.status(404).json({ message: error.message });
253 | }
254 | }
255 |
256 | export default router;
257 |
--------------------------------------------------------------------------------
/server/controllers/user.js:
--------------------------------------------------------------------------------
1 | import bcrypt from 'bcryptjs';
2 | import jwt from 'jsonwebtoken';
3 | import User from '../models/User.js';
4 | import dotenv from 'dotenv';
5 | import { OAuth2Client } from 'google-auth-library';
6 |
7 | dotenv.config();
8 |
9 | const oAuth2Client = new OAuth2Client(
10 | process.env.CLIENT_ID,
11 | process.env.CLIENT_SECRET,
12 | 'postmessage',
13 | );
14 |
15 |
16 | export const signin = async (req, res) => {
17 | const { email, password } = req.body;
18 |
19 | try {
20 | const existingUser = await User.findOne({ email });
21 |
22 | if (!existingUser) {
23 | return res.status(404).json({ message: "User doesn't exist. Please Signup" });
24 | }
25 |
26 | if (existingUser && existingUser?.external_id) {
27 | return res.status(404).json({ message: "Please Sign in with Google" });
28 | }
29 |
30 | const isPasswordCorrect = await bcrypt.compare(password, existingUser.password);
31 |
32 | if (!isPasswordCorrect) {
33 | return res.status(400).json({ message: "Invalid credentials" });
34 | }
35 |
36 | const token = jwt.sign({ id: existingUser._id }, process.env.JWT_SECRET, { expiresIn: "7d" });
37 |
38 | res.status(200).cookie("token", token, { expires: new Date(Date.now() + 604800000), httpOnly: true, sameSite: 'none', secure: true, domain: 'blog-mern-backend2.onrender.com' }).json({ result: existingUser })
39 |
40 | } catch (error) {
41 | console.log(error);
42 | res.status(500).json({ message: "Something went wrong. Please Refresh" });
43 | }
44 | }
45 |
46 | export const signup = async (req, res) => {
47 | const { firstName, lastName, email, password, confirmPassword } = req.body;
48 |
49 | try {
50 | const existingUser = await User.findOne({ email });
51 |
52 | if (existingUser) {
53 | if (existingUser?.external_id) {
54 | return res.status(400).json({ message: "User already exists. Please Sign in with Google" });
55 | }
56 | return res.status(400).json({ message: "User already exists. Please Login" });
57 | }
58 |
59 | if (password !== confirmPassword) {
60 | return res.status(400).json({ message: "Password don't match" });
61 | }
62 |
63 | const hashedPassword = await bcrypt.hash(password, 12);
64 |
65 | const newUser = await User.create({ email, password: hashedPassword, name: `${firstName} ${lastName}` });
66 |
67 | const token = jwt.sign({ id: newUser._id }, process.env.JWT_SECRET, { expiresIn: "7d" });
68 |
69 | res.status(201).cookie("token", token, { expires: new Date(Date.now() + 604800000), httpOnly: true, sameSite: 'none', secure: true, domain: 'blog-mern-backend2.onrender.com' }).json({ result: newUser })
70 | }
71 | catch (error) {
72 | console.log(error);
73 | res.status(500).json({ message: "Something went wrong. Please Refresh" });
74 | }
75 | }
76 |
77 | export const googleSignin = async (req, res) => {
78 |
79 | const { tokens } = await oAuth2Client.getToken(req.body.data); // exchange code for tokens
80 | oAuth2Client.setCredentials({ tokens });
81 |
82 | const result = await verifyToken(tokens?.id_token);
83 | const { name, email, picture, sub, iss, email_verified } = result;
84 |
85 | if (email_verified) {
86 | const existingUser = await User.findOne({ external_id: result.sub });
87 | try {
88 | if (!existingUser) {
89 |
90 | const newUser = await User.create({ name, email, imgUrl: picture, external_type: iss, external_id: sub });
91 |
92 | const token = jwt.sign({ id: newUser._id }, process.env.JWT_SECRET, { expiresIn: "7d" });
93 |
94 | res.status(201).cookie("token", token, { expires: new Date(Date.now() + 604800000), httpOnly: true, sameSite: 'none', secure: true, domain: 'blog-mern-backend2.onrender.com' }).json({ result: newUser })
95 | }
96 | else {
97 | const token = jwt.sign({ id: existingUser._id }, process.env.JWT_SECRET, { expiresIn: "7d" });
98 |
99 | res.status(200).cookie("token", token, { expires: new Date(Date.now() + 604800000), httpOnly: true, sameSite: 'none', secure: true, domain: 'blog-mern-backend2.onrender.com' }).json({ result: existingUser })
100 | }
101 | } catch (error) {
102 | console.log(error);
103 | res.status(400).send(error);
104 | }
105 | }
106 | else {
107 | return res.status(400).json({ message: "Google login failed try again" });
108 | }
109 | }
110 |
111 | export const getRefreshTokens = async (req, res) => {
112 |
113 | const user = new UserRefreshClient(
114 | clientId,
115 | clientSecret,
116 | req.body.refreshToken,
117 | );
118 | const { credentials } = await user.refreshAccessToken(); // optain new tokens
119 | oAuth2Client.setCredentials({ tokens });
120 |
121 | res.json(credentials);
122 | }
123 |
124 | export const verifyToken = async (id_token) => {
125 | try {
126 | const result = await oAuth2Client.verifyIdToken({
127 | idToken: id_token,
128 | audience: process.env.CLIENT_ID,
129 | });
130 | const payload = result.getPayload();
131 | return payload;
132 | } catch (error) {
133 | console.log(error);
134 | }
135 | }
136 |
137 | export const signout = async (req, res) => {
138 | try {
139 | res.clearCookie('token', {
140 | expires: new Date(Date.now()),
141 | httpOnly: true,
142 | sameSite: 'none',
143 | secure: true,
144 | domain: 'blog-mern-backend2.onrender.com'
145 | });
146 | res.status(201).json({ message: "Successfully logged out" });
147 | } catch (error) {
148 | res.status(500).json({ message: "Something went wrong" });
149 | }
150 | }
151 |
152 | export const checkUserStatus = async (req, res) => {
153 | res.status(200).send({ message: 'Token not expired' });
154 | }
--------------------------------------------------------------------------------
/server/index.js:
--------------------------------------------------------------------------------
1 | import express from "express";
2 | import mongoose from 'mongoose';
3 | import cors from 'cors';
4 | import cookieParser from 'cookie-parser';
5 | import dotenv from 'dotenv';
6 | import userRoutes from './routes/users.js';
7 | import blogRoutes from './routes/blogs.js';
8 |
9 | dotenv.config();
10 | const app = express();
11 | const port = process.env.PORT || 5000;
12 |
13 | app.use(express.json({limit: "10mb"}));
14 | app.use(cors({
15 | origin: ['http://localhost:3000', 'https://blog-app-mern-drab.vercel.app'],
16 | credentials: true,
17 | }));
18 | app.use(cookieParser());
19 | app.use(express.urlencoded({limit: '10mb', extended: true}));
20 |
21 | app.use('/blog', blogRoutes);
22 | app.use('/auth', userRoutes);
23 |
24 | mongoose.connect(process.env.MONGODB_URI)
25 | .then(() => app.listen(port, ()=> console.log(`Server is listening on port: ${port}`)))
26 | .catch((error) => console.log(`${error} did not connect`));
27 |
--------------------------------------------------------------------------------
/server/middleware/auth.js:
--------------------------------------------------------------------------------
1 | import jwt from 'jsonwebtoken';
2 | import User from '../models/User.js';
3 |
4 | const auth = async(req, res, next) => {
5 | const { token } = req.cookies;
6 |
7 | if(!token) {
8 | return res.status(401).json({ message: "Please Login to access." });
9 | }
10 |
11 | try {
12 | const decodedData = jwt.verify(token, process.env.JWT_SECRET);
13 | req.user = await User.findById(decodedData.id);
14 | next();
15 |
16 | } catch (error) {
17 | console.log(error);
18 | return res.status(401).json({ msg: 'Token is not valid' });
19 | }
20 | }
21 |
22 | export default auth;
--------------------------------------------------------------------------------
/server/models/Blog.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 |
3 | const blogSchema = new mongoose.Schema({
4 | title: {
5 | type: String,
6 | required: true,
7 | trim: true,
8 | index: true,
9 | },
10 | content: {
11 | type: String,
12 | required: true,
13 | unique: true,
14 | trim: true,
15 | index: true,
16 | },
17 | tags: [{
18 | type: String,
19 | index: true,
20 | }],
21 | author: {
22 | type: mongoose.Schema.Types.ObjectId,
23 | ref: 'User',
24 | required: true,
25 | },
26 | likes: [{
27 | type: mongoose.Schema.Types.ObjectId,
28 | ref: "User",
29 | }],
30 | comments: [{
31 | user: {
32 | type: mongoose.Schema.Types.ObjectId,
33 | ref: "User",
34 | },
35 | comment: {
36 | type: String,
37 | required: true,
38 | trim: true,
39 | },
40 | createdAt: {
41 | type: Date,
42 | default: Date.now,
43 | },
44 | }],
45 | createdAt: {
46 | type: Date,
47 | default: Date.now,
48 | },
49 | });
50 | // blogSchema.index({title: 'text', content: 'text', tags: 'text'});
51 | export default mongoose.model('Blog', blogSchema);
--------------------------------------------------------------------------------
/server/models/Tag.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 |
3 | const tagSchema = new mongoose.Schema({
4 | name: String
5 | });
6 |
7 | export default mongoose.model('Tag', tagSchema);
--------------------------------------------------------------------------------
/server/models/User.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 |
3 | const userSchema = new mongoose.Schema({
4 | name: {
5 | type: String,
6 | required: true,
7 | trim: true,
8 | },
9 | email: {
10 | type: String,
11 | required: true,
12 | unique: true,
13 | trim: true,
14 | },
15 | password: {
16 | type: String,
17 | required: function() {
18 | return !this.external_id;
19 | },
20 | trim: true,
21 | minlength: 8,
22 | },
23 | blogsSaved: [{
24 | type: mongoose.Schema.Types.ObjectId,
25 | ref: 'Blog',
26 | }],
27 | blogsCreated: [{
28 | type: mongoose.Schema.Types.ObjectId,
29 | ref: 'Blog',
30 | }],
31 | imgUrl: {
32 | type: String,
33 | },
34 | external_type: {
35 | type: String,
36 | },
37 | external_id: {
38 | type: String,
39 | required: function() {
40 | return !this.password;
41 | },
42 | },
43 | },{timestamps: true})
44 |
45 | export default mongoose.model('User', userSchema);
--------------------------------------------------------------------------------
/server/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "server",
3 | "version": "1.0.0",
4 | "lockfileVersion": 3,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "server",
9 | "version": "1.0.0",
10 | "license": "ISC",
11 | "dependencies": {
12 | "bcryptjs": "^2.4.3",
13 | "cookie-parser": "^1.4.6",
14 | "cors": "^2.8.5",
15 | "dotenv": "^16.3.1",
16 | "express": "^4.18.2",
17 | "google-auth-library": "^9.0.0",
18 | "jsonwebtoken": "^9.0.1",
19 | "mongoose": "^7.4.0",
20 | "nodemon": "^3.0.1"
21 | }
22 | },
23 | "node_modules/@mongodb-js/saslprep": {
24 | "version": "1.2.0",
25 | "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.2.0.tgz",
26 | "integrity": "sha512-+ywrb0AqkfaYuhHs6LxKWgqbh3I72EpEgESCw37o+9qPx9WTCkgDm2B+eMrwehGtHBWHFU4GXvnSCNiFhhausg==",
27 | "license": "MIT",
28 | "optional": true,
29 | "dependencies": {
30 | "sparse-bitfield": "^3.0.3"
31 | }
32 | },
33 | "node_modules/@types/node": {
34 | "version": "22.13.11",
35 | "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.11.tgz",
36 | "integrity": "sha512-iEUCUJoU0i3VnrCmgoWCXttklWcvoCIx4jzcP22fioIVSdTmjgoEvmAO/QPw6TcS9k5FrNgn4w7q5lGOd1CT5g==",
37 | "license": "MIT",
38 | "dependencies": {
39 | "undici-types": "~6.20.0"
40 | }
41 | },
42 | "node_modules/@types/webidl-conversions": {
43 | "version": "7.0.3",
44 | "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz",
45 | "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==",
46 | "license": "MIT"
47 | },
48 | "node_modules/@types/whatwg-url": {
49 | "version": "8.2.2",
50 | "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz",
51 | "integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==",
52 | "license": "MIT",
53 | "dependencies": {
54 | "@types/node": "*",
55 | "@types/webidl-conversions": "*"
56 | }
57 | },
58 | "node_modules/accepts": {
59 | "version": "1.3.8",
60 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
61 | "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
62 | "license": "MIT",
63 | "dependencies": {
64 | "mime-types": "~2.1.34",
65 | "negotiator": "0.6.3"
66 | },
67 | "engines": {
68 | "node": ">= 0.6"
69 | }
70 | },
71 | "node_modules/agent-base": {
72 | "version": "7.1.3",
73 | "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz",
74 | "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==",
75 | "license": "MIT",
76 | "engines": {
77 | "node": ">= 14"
78 | }
79 | },
80 | "node_modules/anymatch": {
81 | "version": "3.1.3",
82 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
83 | "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
84 | "license": "ISC",
85 | "dependencies": {
86 | "normalize-path": "^3.0.0",
87 | "picomatch": "^2.0.4"
88 | },
89 | "engines": {
90 | "node": ">= 8"
91 | }
92 | },
93 | "node_modules/array-flatten": {
94 | "version": "1.1.1",
95 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
96 | "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
97 | "license": "MIT"
98 | },
99 | "node_modules/balanced-match": {
100 | "version": "1.0.2",
101 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
102 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
103 | "license": "MIT"
104 | },
105 | "node_modules/base64-js": {
106 | "version": "1.5.1",
107 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
108 | "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
109 | "funding": [
110 | {
111 | "type": "github",
112 | "url": "https://github.com/sponsors/feross"
113 | },
114 | {
115 | "type": "patreon",
116 | "url": "https://www.patreon.com/feross"
117 | },
118 | {
119 | "type": "consulting",
120 | "url": "https://feross.org/support"
121 | }
122 | ],
123 | "license": "MIT"
124 | },
125 | "node_modules/bcryptjs": {
126 | "version": "2.4.3",
127 | "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
128 | "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==",
129 | "license": "MIT"
130 | },
131 | "node_modules/bignumber.js": {
132 | "version": "9.1.2",
133 | "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz",
134 | "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==",
135 | "license": "MIT",
136 | "engines": {
137 | "node": "*"
138 | }
139 | },
140 | "node_modules/binary-extensions": {
141 | "version": "2.3.0",
142 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
143 | "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
144 | "license": "MIT",
145 | "engines": {
146 | "node": ">=8"
147 | },
148 | "funding": {
149 | "url": "https://github.com/sponsors/sindresorhus"
150 | }
151 | },
152 | "node_modules/body-parser": {
153 | "version": "1.20.3",
154 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
155 | "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
156 | "license": "MIT",
157 | "dependencies": {
158 | "bytes": "3.1.2",
159 | "content-type": "~1.0.5",
160 | "debug": "2.6.9",
161 | "depd": "2.0.0",
162 | "destroy": "1.2.0",
163 | "http-errors": "2.0.0",
164 | "iconv-lite": "0.4.24",
165 | "on-finished": "2.4.1",
166 | "qs": "6.13.0",
167 | "raw-body": "2.5.2",
168 | "type-is": "~1.6.18",
169 | "unpipe": "1.0.0"
170 | },
171 | "engines": {
172 | "node": ">= 0.8",
173 | "npm": "1.2.8000 || >= 1.4.16"
174 | }
175 | },
176 | "node_modules/brace-expansion": {
177 | "version": "1.1.11",
178 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
179 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
180 | "license": "MIT",
181 | "dependencies": {
182 | "balanced-match": "^1.0.0",
183 | "concat-map": "0.0.1"
184 | }
185 | },
186 | "node_modules/braces": {
187 | "version": "3.0.3",
188 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
189 | "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
190 | "license": "MIT",
191 | "dependencies": {
192 | "fill-range": "^7.1.1"
193 | },
194 | "engines": {
195 | "node": ">=8"
196 | }
197 | },
198 | "node_modules/bson": {
199 | "version": "5.5.1",
200 | "resolved": "https://registry.npmjs.org/bson/-/bson-5.5.1.tgz",
201 | "integrity": "sha512-ix0EwukN2EpC0SRWIj/7B5+A6uQMQy6KMREI9qQqvgpkV2frH63T0UDVd1SYedL6dNCmDBYB3QtXi4ISk9YT+g==",
202 | "license": "Apache-2.0",
203 | "engines": {
204 | "node": ">=14.20.1"
205 | }
206 | },
207 | "node_modules/buffer-equal-constant-time": {
208 | "version": "1.0.1",
209 | "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
210 | "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
211 | "license": "BSD-3-Clause"
212 | },
213 | "node_modules/bytes": {
214 | "version": "3.1.2",
215 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
216 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
217 | "license": "MIT",
218 | "engines": {
219 | "node": ">= 0.8"
220 | }
221 | },
222 | "node_modules/call-bind-apply-helpers": {
223 | "version": "1.0.2",
224 | "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
225 | "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
226 | "license": "MIT",
227 | "dependencies": {
228 | "es-errors": "^1.3.0",
229 | "function-bind": "^1.1.2"
230 | },
231 | "engines": {
232 | "node": ">= 0.4"
233 | }
234 | },
235 | "node_modules/call-bound": {
236 | "version": "1.0.4",
237 | "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
238 | "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
239 | "license": "MIT",
240 | "dependencies": {
241 | "call-bind-apply-helpers": "^1.0.2",
242 | "get-intrinsic": "^1.3.0"
243 | },
244 | "engines": {
245 | "node": ">= 0.4"
246 | },
247 | "funding": {
248 | "url": "https://github.com/sponsors/ljharb"
249 | }
250 | },
251 | "node_modules/chokidar": {
252 | "version": "3.6.0",
253 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
254 | "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
255 | "license": "MIT",
256 | "dependencies": {
257 | "anymatch": "~3.1.2",
258 | "braces": "~3.0.2",
259 | "glob-parent": "~5.1.2",
260 | "is-binary-path": "~2.1.0",
261 | "is-glob": "~4.0.1",
262 | "normalize-path": "~3.0.0",
263 | "readdirp": "~3.6.0"
264 | },
265 | "engines": {
266 | "node": ">= 8.10.0"
267 | },
268 | "funding": {
269 | "url": "https://paulmillr.com/funding/"
270 | },
271 | "optionalDependencies": {
272 | "fsevents": "~2.3.2"
273 | }
274 | },
275 | "node_modules/concat-map": {
276 | "version": "0.0.1",
277 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
278 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
279 | "license": "MIT"
280 | },
281 | "node_modules/content-disposition": {
282 | "version": "0.5.4",
283 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
284 | "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
285 | "license": "MIT",
286 | "dependencies": {
287 | "safe-buffer": "5.2.1"
288 | },
289 | "engines": {
290 | "node": ">= 0.6"
291 | }
292 | },
293 | "node_modules/content-type": {
294 | "version": "1.0.5",
295 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
296 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
297 | "license": "MIT",
298 | "engines": {
299 | "node": ">= 0.6"
300 | }
301 | },
302 | "node_modules/cookie": {
303 | "version": "0.7.2",
304 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
305 | "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
306 | "license": "MIT",
307 | "engines": {
308 | "node": ">= 0.6"
309 | }
310 | },
311 | "node_modules/cookie-parser": {
312 | "version": "1.4.7",
313 | "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz",
314 | "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==",
315 | "license": "MIT",
316 | "dependencies": {
317 | "cookie": "0.7.2",
318 | "cookie-signature": "1.0.6"
319 | },
320 | "engines": {
321 | "node": ">= 0.8.0"
322 | }
323 | },
324 | "node_modules/cookie-signature": {
325 | "version": "1.0.6",
326 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
327 | "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
328 | "license": "MIT"
329 | },
330 | "node_modules/cors": {
331 | "version": "2.8.5",
332 | "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
333 | "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
334 | "license": "MIT",
335 | "dependencies": {
336 | "object-assign": "^4",
337 | "vary": "^1"
338 | },
339 | "engines": {
340 | "node": ">= 0.10"
341 | }
342 | },
343 | "node_modules/debug": {
344 | "version": "2.6.9",
345 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
346 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
347 | "license": "MIT",
348 | "dependencies": {
349 | "ms": "2.0.0"
350 | }
351 | },
352 | "node_modules/depd": {
353 | "version": "2.0.0",
354 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
355 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
356 | "license": "MIT",
357 | "engines": {
358 | "node": ">= 0.8"
359 | }
360 | },
361 | "node_modules/destroy": {
362 | "version": "1.2.0",
363 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
364 | "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
365 | "license": "MIT",
366 | "engines": {
367 | "node": ">= 0.8",
368 | "npm": "1.2.8000 || >= 1.4.16"
369 | }
370 | },
371 | "node_modules/dotenv": {
372 | "version": "16.4.7",
373 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz",
374 | "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==",
375 | "license": "BSD-2-Clause",
376 | "engines": {
377 | "node": ">=12"
378 | },
379 | "funding": {
380 | "url": "https://dotenvx.com"
381 | }
382 | },
383 | "node_modules/dunder-proto": {
384 | "version": "1.0.1",
385 | "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
386 | "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
387 | "license": "MIT",
388 | "dependencies": {
389 | "call-bind-apply-helpers": "^1.0.1",
390 | "es-errors": "^1.3.0",
391 | "gopd": "^1.2.0"
392 | },
393 | "engines": {
394 | "node": ">= 0.4"
395 | }
396 | },
397 | "node_modules/ecdsa-sig-formatter": {
398 | "version": "1.0.11",
399 | "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
400 | "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
401 | "license": "Apache-2.0",
402 | "dependencies": {
403 | "safe-buffer": "^5.0.1"
404 | }
405 | },
406 | "node_modules/ee-first": {
407 | "version": "1.1.1",
408 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
409 | "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
410 | "license": "MIT"
411 | },
412 | "node_modules/encodeurl": {
413 | "version": "2.0.0",
414 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
415 | "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
416 | "license": "MIT",
417 | "engines": {
418 | "node": ">= 0.8"
419 | }
420 | },
421 | "node_modules/es-define-property": {
422 | "version": "1.0.1",
423 | "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
424 | "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
425 | "license": "MIT",
426 | "engines": {
427 | "node": ">= 0.4"
428 | }
429 | },
430 | "node_modules/es-errors": {
431 | "version": "1.3.0",
432 | "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
433 | "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
434 | "license": "MIT",
435 | "engines": {
436 | "node": ">= 0.4"
437 | }
438 | },
439 | "node_modules/es-object-atoms": {
440 | "version": "1.1.1",
441 | "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
442 | "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
443 | "license": "MIT",
444 | "dependencies": {
445 | "es-errors": "^1.3.0"
446 | },
447 | "engines": {
448 | "node": ">= 0.4"
449 | }
450 | },
451 | "node_modules/escape-html": {
452 | "version": "1.0.3",
453 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
454 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
455 | "license": "MIT"
456 | },
457 | "node_modules/etag": {
458 | "version": "1.8.1",
459 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
460 | "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
461 | "license": "MIT",
462 | "engines": {
463 | "node": ">= 0.6"
464 | }
465 | },
466 | "node_modules/express": {
467 | "version": "4.21.2",
468 | "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
469 | "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
470 | "license": "MIT",
471 | "dependencies": {
472 | "accepts": "~1.3.8",
473 | "array-flatten": "1.1.1",
474 | "body-parser": "1.20.3",
475 | "content-disposition": "0.5.4",
476 | "content-type": "~1.0.4",
477 | "cookie": "0.7.1",
478 | "cookie-signature": "1.0.6",
479 | "debug": "2.6.9",
480 | "depd": "2.0.0",
481 | "encodeurl": "~2.0.0",
482 | "escape-html": "~1.0.3",
483 | "etag": "~1.8.1",
484 | "finalhandler": "1.3.1",
485 | "fresh": "0.5.2",
486 | "http-errors": "2.0.0",
487 | "merge-descriptors": "1.0.3",
488 | "methods": "~1.1.2",
489 | "on-finished": "2.4.1",
490 | "parseurl": "~1.3.3",
491 | "path-to-regexp": "0.1.12",
492 | "proxy-addr": "~2.0.7",
493 | "qs": "6.13.0",
494 | "range-parser": "~1.2.1",
495 | "safe-buffer": "5.2.1",
496 | "send": "0.19.0",
497 | "serve-static": "1.16.2",
498 | "setprototypeof": "1.2.0",
499 | "statuses": "2.0.1",
500 | "type-is": "~1.6.18",
501 | "utils-merge": "1.0.1",
502 | "vary": "~1.1.2"
503 | },
504 | "engines": {
505 | "node": ">= 0.10.0"
506 | },
507 | "funding": {
508 | "type": "opencollective",
509 | "url": "https://opencollective.com/express"
510 | }
511 | },
512 | "node_modules/express/node_modules/cookie": {
513 | "version": "0.7.1",
514 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
515 | "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
516 | "license": "MIT",
517 | "engines": {
518 | "node": ">= 0.6"
519 | }
520 | },
521 | "node_modules/extend": {
522 | "version": "3.0.2",
523 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
524 | "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
525 | "license": "MIT"
526 | },
527 | "node_modules/fill-range": {
528 | "version": "7.1.1",
529 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
530 | "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
531 | "license": "MIT",
532 | "dependencies": {
533 | "to-regex-range": "^5.0.1"
534 | },
535 | "engines": {
536 | "node": ">=8"
537 | }
538 | },
539 | "node_modules/finalhandler": {
540 | "version": "1.3.1",
541 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
542 | "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
543 | "license": "MIT",
544 | "dependencies": {
545 | "debug": "2.6.9",
546 | "encodeurl": "~2.0.0",
547 | "escape-html": "~1.0.3",
548 | "on-finished": "2.4.1",
549 | "parseurl": "~1.3.3",
550 | "statuses": "2.0.1",
551 | "unpipe": "~1.0.0"
552 | },
553 | "engines": {
554 | "node": ">= 0.8"
555 | }
556 | },
557 | "node_modules/forwarded": {
558 | "version": "0.2.0",
559 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
560 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
561 | "license": "MIT",
562 | "engines": {
563 | "node": ">= 0.6"
564 | }
565 | },
566 | "node_modules/fresh": {
567 | "version": "0.5.2",
568 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
569 | "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
570 | "license": "MIT",
571 | "engines": {
572 | "node": ">= 0.6"
573 | }
574 | },
575 | "node_modules/fsevents": {
576 | "version": "2.3.3",
577 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
578 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
579 | "hasInstallScript": true,
580 | "license": "MIT",
581 | "optional": true,
582 | "os": [
583 | "darwin"
584 | ],
585 | "engines": {
586 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
587 | }
588 | },
589 | "node_modules/function-bind": {
590 | "version": "1.1.2",
591 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
592 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
593 | "license": "MIT",
594 | "funding": {
595 | "url": "https://github.com/sponsors/ljharb"
596 | }
597 | },
598 | "node_modules/gaxios": {
599 | "version": "6.7.1",
600 | "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz",
601 | "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==",
602 | "license": "Apache-2.0",
603 | "dependencies": {
604 | "extend": "^3.0.2",
605 | "https-proxy-agent": "^7.0.1",
606 | "is-stream": "^2.0.0",
607 | "node-fetch": "^2.6.9",
608 | "uuid": "^9.0.1"
609 | },
610 | "engines": {
611 | "node": ">=14"
612 | }
613 | },
614 | "node_modules/gcp-metadata": {
615 | "version": "6.1.1",
616 | "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz",
617 | "integrity": "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==",
618 | "license": "Apache-2.0",
619 | "dependencies": {
620 | "gaxios": "^6.1.1",
621 | "google-logging-utils": "^0.0.2",
622 | "json-bigint": "^1.0.0"
623 | },
624 | "engines": {
625 | "node": ">=14"
626 | }
627 | },
628 | "node_modules/get-intrinsic": {
629 | "version": "1.3.0",
630 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
631 | "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
632 | "license": "MIT",
633 | "dependencies": {
634 | "call-bind-apply-helpers": "^1.0.2",
635 | "es-define-property": "^1.0.1",
636 | "es-errors": "^1.3.0",
637 | "es-object-atoms": "^1.1.1",
638 | "function-bind": "^1.1.2",
639 | "get-proto": "^1.0.1",
640 | "gopd": "^1.2.0",
641 | "has-symbols": "^1.1.0",
642 | "hasown": "^2.0.2",
643 | "math-intrinsics": "^1.1.0"
644 | },
645 | "engines": {
646 | "node": ">= 0.4"
647 | },
648 | "funding": {
649 | "url": "https://github.com/sponsors/ljharb"
650 | }
651 | },
652 | "node_modules/get-proto": {
653 | "version": "1.0.1",
654 | "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
655 | "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
656 | "license": "MIT",
657 | "dependencies": {
658 | "dunder-proto": "^1.0.1",
659 | "es-object-atoms": "^1.0.0"
660 | },
661 | "engines": {
662 | "node": ">= 0.4"
663 | }
664 | },
665 | "node_modules/glob-parent": {
666 | "version": "5.1.2",
667 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
668 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
669 | "license": "ISC",
670 | "dependencies": {
671 | "is-glob": "^4.0.1"
672 | },
673 | "engines": {
674 | "node": ">= 6"
675 | }
676 | },
677 | "node_modules/google-auth-library": {
678 | "version": "9.15.1",
679 | "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz",
680 | "integrity": "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==",
681 | "license": "Apache-2.0",
682 | "dependencies": {
683 | "base64-js": "^1.3.0",
684 | "ecdsa-sig-formatter": "^1.0.11",
685 | "gaxios": "^6.1.1",
686 | "gcp-metadata": "^6.1.0",
687 | "gtoken": "^7.0.0",
688 | "jws": "^4.0.0"
689 | },
690 | "engines": {
691 | "node": ">=14"
692 | }
693 | },
694 | "node_modules/google-logging-utils": {
695 | "version": "0.0.2",
696 | "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-0.0.2.tgz",
697 | "integrity": "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==",
698 | "license": "Apache-2.0",
699 | "engines": {
700 | "node": ">=14"
701 | }
702 | },
703 | "node_modules/gopd": {
704 | "version": "1.2.0",
705 | "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
706 | "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
707 | "license": "MIT",
708 | "engines": {
709 | "node": ">= 0.4"
710 | },
711 | "funding": {
712 | "url": "https://github.com/sponsors/ljharb"
713 | }
714 | },
715 | "node_modules/gtoken": {
716 | "version": "7.1.0",
717 | "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz",
718 | "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==",
719 | "license": "MIT",
720 | "dependencies": {
721 | "gaxios": "^6.0.0",
722 | "jws": "^4.0.0"
723 | },
724 | "engines": {
725 | "node": ">=14.0.0"
726 | }
727 | },
728 | "node_modules/has-flag": {
729 | "version": "3.0.0",
730 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
731 | "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
732 | "license": "MIT",
733 | "engines": {
734 | "node": ">=4"
735 | }
736 | },
737 | "node_modules/has-symbols": {
738 | "version": "1.1.0",
739 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
740 | "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
741 | "license": "MIT",
742 | "engines": {
743 | "node": ">= 0.4"
744 | },
745 | "funding": {
746 | "url": "https://github.com/sponsors/ljharb"
747 | }
748 | },
749 | "node_modules/hasown": {
750 | "version": "2.0.2",
751 | "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
752 | "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
753 | "license": "MIT",
754 | "dependencies": {
755 | "function-bind": "^1.1.2"
756 | },
757 | "engines": {
758 | "node": ">= 0.4"
759 | }
760 | },
761 | "node_modules/http-errors": {
762 | "version": "2.0.0",
763 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
764 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
765 | "license": "MIT",
766 | "dependencies": {
767 | "depd": "2.0.0",
768 | "inherits": "2.0.4",
769 | "setprototypeof": "1.2.0",
770 | "statuses": "2.0.1",
771 | "toidentifier": "1.0.1"
772 | },
773 | "engines": {
774 | "node": ">= 0.8"
775 | }
776 | },
777 | "node_modules/https-proxy-agent": {
778 | "version": "7.0.6",
779 | "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
780 | "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
781 | "license": "MIT",
782 | "dependencies": {
783 | "agent-base": "^7.1.2",
784 | "debug": "4"
785 | },
786 | "engines": {
787 | "node": ">= 14"
788 | }
789 | },
790 | "node_modules/https-proxy-agent/node_modules/debug": {
791 | "version": "4.4.0",
792 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
793 | "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
794 | "license": "MIT",
795 | "dependencies": {
796 | "ms": "^2.1.3"
797 | },
798 | "engines": {
799 | "node": ">=6.0"
800 | },
801 | "peerDependenciesMeta": {
802 | "supports-color": {
803 | "optional": true
804 | }
805 | }
806 | },
807 | "node_modules/https-proxy-agent/node_modules/ms": {
808 | "version": "2.1.3",
809 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
810 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
811 | "license": "MIT"
812 | },
813 | "node_modules/iconv-lite": {
814 | "version": "0.4.24",
815 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
816 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
817 | "license": "MIT",
818 | "dependencies": {
819 | "safer-buffer": ">= 2.1.2 < 3"
820 | },
821 | "engines": {
822 | "node": ">=0.10.0"
823 | }
824 | },
825 | "node_modules/ignore-by-default": {
826 | "version": "1.0.1",
827 | "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
828 | "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==",
829 | "license": "ISC"
830 | },
831 | "node_modules/inherits": {
832 | "version": "2.0.4",
833 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
834 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
835 | "license": "ISC"
836 | },
837 | "node_modules/ip-address": {
838 | "version": "9.0.5",
839 | "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz",
840 | "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==",
841 | "license": "MIT",
842 | "dependencies": {
843 | "jsbn": "1.1.0",
844 | "sprintf-js": "^1.1.3"
845 | },
846 | "engines": {
847 | "node": ">= 12"
848 | }
849 | },
850 | "node_modules/ipaddr.js": {
851 | "version": "1.9.1",
852 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
853 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
854 | "license": "MIT",
855 | "engines": {
856 | "node": ">= 0.10"
857 | }
858 | },
859 | "node_modules/is-binary-path": {
860 | "version": "2.1.0",
861 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
862 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
863 | "license": "MIT",
864 | "dependencies": {
865 | "binary-extensions": "^2.0.0"
866 | },
867 | "engines": {
868 | "node": ">=8"
869 | }
870 | },
871 | "node_modules/is-extglob": {
872 | "version": "2.1.1",
873 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
874 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
875 | "license": "MIT",
876 | "engines": {
877 | "node": ">=0.10.0"
878 | }
879 | },
880 | "node_modules/is-glob": {
881 | "version": "4.0.3",
882 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
883 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
884 | "license": "MIT",
885 | "dependencies": {
886 | "is-extglob": "^2.1.1"
887 | },
888 | "engines": {
889 | "node": ">=0.10.0"
890 | }
891 | },
892 | "node_modules/is-number": {
893 | "version": "7.0.0",
894 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
895 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
896 | "license": "MIT",
897 | "engines": {
898 | "node": ">=0.12.0"
899 | }
900 | },
901 | "node_modules/is-stream": {
902 | "version": "2.0.1",
903 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
904 | "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
905 | "license": "MIT",
906 | "engines": {
907 | "node": ">=8"
908 | },
909 | "funding": {
910 | "url": "https://github.com/sponsors/sindresorhus"
911 | }
912 | },
913 | "node_modules/jsbn": {
914 | "version": "1.1.0",
915 | "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz",
916 | "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==",
917 | "license": "MIT"
918 | },
919 | "node_modules/json-bigint": {
920 | "version": "1.0.0",
921 | "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz",
922 | "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==",
923 | "license": "MIT",
924 | "dependencies": {
925 | "bignumber.js": "^9.0.0"
926 | }
927 | },
928 | "node_modules/jsonwebtoken": {
929 | "version": "9.0.2",
930 | "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
931 | "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
932 | "license": "MIT",
933 | "dependencies": {
934 | "jws": "^3.2.2",
935 | "lodash.includes": "^4.3.0",
936 | "lodash.isboolean": "^3.0.3",
937 | "lodash.isinteger": "^4.0.4",
938 | "lodash.isnumber": "^3.0.3",
939 | "lodash.isplainobject": "^4.0.6",
940 | "lodash.isstring": "^4.0.1",
941 | "lodash.once": "^4.0.0",
942 | "ms": "^2.1.1",
943 | "semver": "^7.5.4"
944 | },
945 | "engines": {
946 | "node": ">=12",
947 | "npm": ">=6"
948 | }
949 | },
950 | "node_modules/jsonwebtoken/node_modules/jwa": {
951 | "version": "1.4.1",
952 | "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
953 | "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
954 | "license": "MIT",
955 | "dependencies": {
956 | "buffer-equal-constant-time": "1.0.1",
957 | "ecdsa-sig-formatter": "1.0.11",
958 | "safe-buffer": "^5.0.1"
959 | }
960 | },
961 | "node_modules/jsonwebtoken/node_modules/jws": {
962 | "version": "3.2.2",
963 | "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
964 | "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
965 | "license": "MIT",
966 | "dependencies": {
967 | "jwa": "^1.4.1",
968 | "safe-buffer": "^5.0.1"
969 | }
970 | },
971 | "node_modules/jsonwebtoken/node_modules/ms": {
972 | "version": "2.1.3",
973 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
974 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
975 | "license": "MIT"
976 | },
977 | "node_modules/jwa": {
978 | "version": "2.0.0",
979 | "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz",
980 | "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==",
981 | "license": "MIT",
982 | "dependencies": {
983 | "buffer-equal-constant-time": "1.0.1",
984 | "ecdsa-sig-formatter": "1.0.11",
985 | "safe-buffer": "^5.0.1"
986 | }
987 | },
988 | "node_modules/jws": {
989 | "version": "4.0.0",
990 | "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz",
991 | "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==",
992 | "license": "MIT",
993 | "dependencies": {
994 | "jwa": "^2.0.0",
995 | "safe-buffer": "^5.0.1"
996 | }
997 | },
998 | "node_modules/kareem": {
999 | "version": "2.5.1",
1000 | "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.5.1.tgz",
1001 | "integrity": "sha512-7jFxRVm+jD+rkq3kY0iZDJfsO2/t4BBPeEb2qKn2lR/9KhuksYk5hxzfRYWMPV8P/x2d0kHD306YyWLzjjH+uA==",
1002 | "license": "Apache-2.0",
1003 | "engines": {
1004 | "node": ">=12.0.0"
1005 | }
1006 | },
1007 | "node_modules/lodash.includes": {
1008 | "version": "4.3.0",
1009 | "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
1010 | "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
1011 | "license": "MIT"
1012 | },
1013 | "node_modules/lodash.isboolean": {
1014 | "version": "3.0.3",
1015 | "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
1016 | "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
1017 | "license": "MIT"
1018 | },
1019 | "node_modules/lodash.isinteger": {
1020 | "version": "4.0.4",
1021 | "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
1022 | "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==",
1023 | "license": "MIT"
1024 | },
1025 | "node_modules/lodash.isnumber": {
1026 | "version": "3.0.3",
1027 | "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
1028 | "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==",
1029 | "license": "MIT"
1030 | },
1031 | "node_modules/lodash.isplainobject": {
1032 | "version": "4.0.6",
1033 | "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
1034 | "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
1035 | "license": "MIT"
1036 | },
1037 | "node_modules/lodash.isstring": {
1038 | "version": "4.0.1",
1039 | "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
1040 | "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
1041 | "license": "MIT"
1042 | },
1043 | "node_modules/lodash.once": {
1044 | "version": "4.1.1",
1045 | "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
1046 | "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
1047 | "license": "MIT"
1048 | },
1049 | "node_modules/math-intrinsics": {
1050 | "version": "1.1.0",
1051 | "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
1052 | "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
1053 | "license": "MIT",
1054 | "engines": {
1055 | "node": ">= 0.4"
1056 | }
1057 | },
1058 | "node_modules/media-typer": {
1059 | "version": "0.3.0",
1060 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
1061 | "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
1062 | "license": "MIT",
1063 | "engines": {
1064 | "node": ">= 0.6"
1065 | }
1066 | },
1067 | "node_modules/memory-pager": {
1068 | "version": "1.5.0",
1069 | "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
1070 | "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==",
1071 | "license": "MIT",
1072 | "optional": true
1073 | },
1074 | "node_modules/merge-descriptors": {
1075 | "version": "1.0.3",
1076 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
1077 | "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
1078 | "license": "MIT",
1079 | "funding": {
1080 | "url": "https://github.com/sponsors/sindresorhus"
1081 | }
1082 | },
1083 | "node_modules/methods": {
1084 | "version": "1.1.2",
1085 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
1086 | "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
1087 | "license": "MIT",
1088 | "engines": {
1089 | "node": ">= 0.6"
1090 | }
1091 | },
1092 | "node_modules/mime": {
1093 | "version": "1.6.0",
1094 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
1095 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
1096 | "license": "MIT",
1097 | "bin": {
1098 | "mime": "cli.js"
1099 | },
1100 | "engines": {
1101 | "node": ">=4"
1102 | }
1103 | },
1104 | "node_modules/mime-db": {
1105 | "version": "1.52.0",
1106 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
1107 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
1108 | "license": "MIT",
1109 | "engines": {
1110 | "node": ">= 0.6"
1111 | }
1112 | },
1113 | "node_modules/mime-types": {
1114 | "version": "2.1.35",
1115 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
1116 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
1117 | "license": "MIT",
1118 | "dependencies": {
1119 | "mime-db": "1.52.0"
1120 | },
1121 | "engines": {
1122 | "node": ">= 0.6"
1123 | }
1124 | },
1125 | "node_modules/minimatch": {
1126 | "version": "3.1.2",
1127 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
1128 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
1129 | "license": "ISC",
1130 | "dependencies": {
1131 | "brace-expansion": "^1.1.7"
1132 | },
1133 | "engines": {
1134 | "node": "*"
1135 | }
1136 | },
1137 | "node_modules/mongodb-connection-string-url": {
1138 | "version": "2.6.0",
1139 | "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz",
1140 | "integrity": "sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==",
1141 | "license": "Apache-2.0",
1142 | "dependencies": {
1143 | "@types/whatwg-url": "^8.2.1",
1144 | "whatwg-url": "^11.0.0"
1145 | }
1146 | },
1147 | "node_modules/mongodb-connection-string-url/node_modules/tr46": {
1148 | "version": "3.0.0",
1149 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz",
1150 | "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==",
1151 | "license": "MIT",
1152 | "dependencies": {
1153 | "punycode": "^2.1.1"
1154 | },
1155 | "engines": {
1156 | "node": ">=12"
1157 | }
1158 | },
1159 | "node_modules/mongodb-connection-string-url/node_modules/webidl-conversions": {
1160 | "version": "7.0.0",
1161 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
1162 | "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
1163 | "license": "BSD-2-Clause",
1164 | "engines": {
1165 | "node": ">=12"
1166 | }
1167 | },
1168 | "node_modules/mongodb-connection-string-url/node_modules/whatwg-url": {
1169 | "version": "11.0.0",
1170 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz",
1171 | "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==",
1172 | "license": "MIT",
1173 | "dependencies": {
1174 | "tr46": "^3.0.0",
1175 | "webidl-conversions": "^7.0.0"
1176 | },
1177 | "engines": {
1178 | "node": ">=12"
1179 | }
1180 | },
1181 | "node_modules/mongoose": {
1182 | "version": "7.8.6",
1183 | "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-7.8.6.tgz",
1184 | "integrity": "sha512-1oVPRHvcmPVwk/zeSTEzayzQEVeYQM1D5zrkLsttfNNB7pPRUmkKeFu6gpbvyEswOuZLrWJjqB8kSTY+k2AZOA==",
1185 | "license": "MIT",
1186 | "dependencies": {
1187 | "bson": "^5.5.0",
1188 | "kareem": "2.5.1",
1189 | "mongodb": "5.9.2",
1190 | "mpath": "0.9.0",
1191 | "mquery": "5.0.0",
1192 | "ms": "2.1.3",
1193 | "sift": "16.0.1"
1194 | },
1195 | "engines": {
1196 | "node": ">=14.20.1"
1197 | },
1198 | "funding": {
1199 | "type": "opencollective",
1200 | "url": "https://opencollective.com/mongoose"
1201 | }
1202 | },
1203 | "node_modules/mongoose/node_modules/mongodb": {
1204 | "version": "5.9.2",
1205 | "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.9.2.tgz",
1206 | "integrity": "sha512-H60HecKO4Bc+7dhOv4sJlgvenK4fQNqqUIlXxZYQNbfEWSALGAwGoyJd/0Qwk4TttFXUOHJ2ZJQe/52ScaUwtQ==",
1207 | "license": "Apache-2.0",
1208 | "dependencies": {
1209 | "bson": "^5.5.0",
1210 | "mongodb-connection-string-url": "^2.6.0",
1211 | "socks": "^2.7.1"
1212 | },
1213 | "engines": {
1214 | "node": ">=14.20.1"
1215 | },
1216 | "optionalDependencies": {
1217 | "@mongodb-js/saslprep": "^1.1.0"
1218 | },
1219 | "peerDependencies": {
1220 | "@aws-sdk/credential-providers": "^3.188.0",
1221 | "@mongodb-js/zstd": "^1.0.0",
1222 | "kerberos": "^1.0.0 || ^2.0.0",
1223 | "mongodb-client-encryption": ">=2.3.0 <3",
1224 | "snappy": "^7.2.2"
1225 | },
1226 | "peerDependenciesMeta": {
1227 | "@aws-sdk/credential-providers": {
1228 | "optional": true
1229 | },
1230 | "@mongodb-js/zstd": {
1231 | "optional": true
1232 | },
1233 | "kerberos": {
1234 | "optional": true
1235 | },
1236 | "mongodb-client-encryption": {
1237 | "optional": true
1238 | },
1239 | "snappy": {
1240 | "optional": true
1241 | }
1242 | }
1243 | },
1244 | "node_modules/mongoose/node_modules/ms": {
1245 | "version": "2.1.3",
1246 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
1247 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
1248 | "license": "MIT"
1249 | },
1250 | "node_modules/mpath": {
1251 | "version": "0.9.0",
1252 | "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz",
1253 | "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==",
1254 | "license": "MIT",
1255 | "engines": {
1256 | "node": ">=4.0.0"
1257 | }
1258 | },
1259 | "node_modules/mquery": {
1260 | "version": "5.0.0",
1261 | "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz",
1262 | "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==",
1263 | "license": "MIT",
1264 | "dependencies": {
1265 | "debug": "4.x"
1266 | },
1267 | "engines": {
1268 | "node": ">=14.0.0"
1269 | }
1270 | },
1271 | "node_modules/mquery/node_modules/debug": {
1272 | "version": "4.4.0",
1273 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
1274 | "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
1275 | "license": "MIT",
1276 | "dependencies": {
1277 | "ms": "^2.1.3"
1278 | },
1279 | "engines": {
1280 | "node": ">=6.0"
1281 | },
1282 | "peerDependenciesMeta": {
1283 | "supports-color": {
1284 | "optional": true
1285 | }
1286 | }
1287 | },
1288 | "node_modules/mquery/node_modules/ms": {
1289 | "version": "2.1.3",
1290 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
1291 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
1292 | "license": "MIT"
1293 | },
1294 | "node_modules/ms": {
1295 | "version": "2.0.0",
1296 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
1297 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
1298 | "license": "MIT"
1299 | },
1300 | "node_modules/negotiator": {
1301 | "version": "0.6.3",
1302 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
1303 | "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
1304 | "license": "MIT",
1305 | "engines": {
1306 | "node": ">= 0.6"
1307 | }
1308 | },
1309 | "node_modules/node-fetch": {
1310 | "version": "2.7.0",
1311 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
1312 | "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
1313 | "license": "MIT",
1314 | "dependencies": {
1315 | "whatwg-url": "^5.0.0"
1316 | },
1317 | "engines": {
1318 | "node": "4.x || >=6.0.0"
1319 | },
1320 | "peerDependencies": {
1321 | "encoding": "^0.1.0"
1322 | },
1323 | "peerDependenciesMeta": {
1324 | "encoding": {
1325 | "optional": true
1326 | }
1327 | }
1328 | },
1329 | "node_modules/nodemon": {
1330 | "version": "3.1.9",
1331 | "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.9.tgz",
1332 | "integrity": "sha512-hdr1oIb2p6ZSxu3PB2JWWYS7ZQ0qvaZsc3hK8DR8f02kRzc8rjYmxAIvdz+aYC+8F2IjNaB7HMcSDg8nQpJxyg==",
1333 | "license": "MIT",
1334 | "dependencies": {
1335 | "chokidar": "^3.5.2",
1336 | "debug": "^4",
1337 | "ignore-by-default": "^1.0.1",
1338 | "minimatch": "^3.1.2",
1339 | "pstree.remy": "^1.1.8",
1340 | "semver": "^7.5.3",
1341 | "simple-update-notifier": "^2.0.0",
1342 | "supports-color": "^5.5.0",
1343 | "touch": "^3.1.0",
1344 | "undefsafe": "^2.0.5"
1345 | },
1346 | "bin": {
1347 | "nodemon": "bin/nodemon.js"
1348 | },
1349 | "engines": {
1350 | "node": ">=10"
1351 | },
1352 | "funding": {
1353 | "type": "opencollective",
1354 | "url": "https://opencollective.com/nodemon"
1355 | }
1356 | },
1357 | "node_modules/nodemon/node_modules/debug": {
1358 | "version": "4.4.0",
1359 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
1360 | "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
1361 | "license": "MIT",
1362 | "dependencies": {
1363 | "ms": "^2.1.3"
1364 | },
1365 | "engines": {
1366 | "node": ">=6.0"
1367 | },
1368 | "peerDependenciesMeta": {
1369 | "supports-color": {
1370 | "optional": true
1371 | }
1372 | }
1373 | },
1374 | "node_modules/nodemon/node_modules/ms": {
1375 | "version": "2.1.3",
1376 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
1377 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
1378 | "license": "MIT"
1379 | },
1380 | "node_modules/normalize-path": {
1381 | "version": "3.0.0",
1382 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
1383 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
1384 | "license": "MIT",
1385 | "engines": {
1386 | "node": ">=0.10.0"
1387 | }
1388 | },
1389 | "node_modules/object-assign": {
1390 | "version": "4.1.1",
1391 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
1392 | "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
1393 | "license": "MIT",
1394 | "engines": {
1395 | "node": ">=0.10.0"
1396 | }
1397 | },
1398 | "node_modules/object-inspect": {
1399 | "version": "1.13.4",
1400 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
1401 | "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
1402 | "license": "MIT",
1403 | "engines": {
1404 | "node": ">= 0.4"
1405 | },
1406 | "funding": {
1407 | "url": "https://github.com/sponsors/ljharb"
1408 | }
1409 | },
1410 | "node_modules/on-finished": {
1411 | "version": "2.4.1",
1412 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
1413 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
1414 | "license": "MIT",
1415 | "dependencies": {
1416 | "ee-first": "1.1.1"
1417 | },
1418 | "engines": {
1419 | "node": ">= 0.8"
1420 | }
1421 | },
1422 | "node_modules/parseurl": {
1423 | "version": "1.3.3",
1424 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
1425 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
1426 | "license": "MIT",
1427 | "engines": {
1428 | "node": ">= 0.8"
1429 | }
1430 | },
1431 | "node_modules/path-to-regexp": {
1432 | "version": "0.1.12",
1433 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
1434 | "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
1435 | "license": "MIT"
1436 | },
1437 | "node_modules/picomatch": {
1438 | "version": "2.3.1",
1439 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
1440 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
1441 | "license": "MIT",
1442 | "engines": {
1443 | "node": ">=8.6"
1444 | },
1445 | "funding": {
1446 | "url": "https://github.com/sponsors/jonschlinkert"
1447 | }
1448 | },
1449 | "node_modules/proxy-addr": {
1450 | "version": "2.0.7",
1451 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
1452 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
1453 | "license": "MIT",
1454 | "dependencies": {
1455 | "forwarded": "0.2.0",
1456 | "ipaddr.js": "1.9.1"
1457 | },
1458 | "engines": {
1459 | "node": ">= 0.10"
1460 | }
1461 | },
1462 | "node_modules/pstree.remy": {
1463 | "version": "1.1.8",
1464 | "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
1465 | "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==",
1466 | "license": "MIT"
1467 | },
1468 | "node_modules/punycode": {
1469 | "version": "2.3.1",
1470 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
1471 | "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
1472 | "license": "MIT",
1473 | "engines": {
1474 | "node": ">=6"
1475 | }
1476 | },
1477 | "node_modules/qs": {
1478 | "version": "6.13.0",
1479 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
1480 | "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
1481 | "license": "BSD-3-Clause",
1482 | "dependencies": {
1483 | "side-channel": "^1.0.6"
1484 | },
1485 | "engines": {
1486 | "node": ">=0.6"
1487 | },
1488 | "funding": {
1489 | "url": "https://github.com/sponsors/ljharb"
1490 | }
1491 | },
1492 | "node_modules/range-parser": {
1493 | "version": "1.2.1",
1494 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
1495 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
1496 | "license": "MIT",
1497 | "engines": {
1498 | "node": ">= 0.6"
1499 | }
1500 | },
1501 | "node_modules/raw-body": {
1502 | "version": "2.5.2",
1503 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
1504 | "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
1505 | "license": "MIT",
1506 | "dependencies": {
1507 | "bytes": "3.1.2",
1508 | "http-errors": "2.0.0",
1509 | "iconv-lite": "0.4.24",
1510 | "unpipe": "1.0.0"
1511 | },
1512 | "engines": {
1513 | "node": ">= 0.8"
1514 | }
1515 | },
1516 | "node_modules/readdirp": {
1517 | "version": "3.6.0",
1518 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
1519 | "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
1520 | "license": "MIT",
1521 | "dependencies": {
1522 | "picomatch": "^2.2.1"
1523 | },
1524 | "engines": {
1525 | "node": ">=8.10.0"
1526 | }
1527 | },
1528 | "node_modules/safe-buffer": {
1529 | "version": "5.2.1",
1530 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
1531 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
1532 | "funding": [
1533 | {
1534 | "type": "github",
1535 | "url": "https://github.com/sponsors/feross"
1536 | },
1537 | {
1538 | "type": "patreon",
1539 | "url": "https://www.patreon.com/feross"
1540 | },
1541 | {
1542 | "type": "consulting",
1543 | "url": "https://feross.org/support"
1544 | }
1545 | ],
1546 | "license": "MIT"
1547 | },
1548 | "node_modules/safer-buffer": {
1549 | "version": "2.1.2",
1550 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
1551 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
1552 | "license": "MIT"
1553 | },
1554 | "node_modules/semver": {
1555 | "version": "7.7.1",
1556 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
1557 | "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
1558 | "license": "ISC",
1559 | "bin": {
1560 | "semver": "bin/semver.js"
1561 | },
1562 | "engines": {
1563 | "node": ">=10"
1564 | }
1565 | },
1566 | "node_modules/send": {
1567 | "version": "0.19.0",
1568 | "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
1569 | "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
1570 | "license": "MIT",
1571 | "dependencies": {
1572 | "debug": "2.6.9",
1573 | "depd": "2.0.0",
1574 | "destroy": "1.2.0",
1575 | "encodeurl": "~1.0.2",
1576 | "escape-html": "~1.0.3",
1577 | "etag": "~1.8.1",
1578 | "fresh": "0.5.2",
1579 | "http-errors": "2.0.0",
1580 | "mime": "1.6.0",
1581 | "ms": "2.1.3",
1582 | "on-finished": "2.4.1",
1583 | "range-parser": "~1.2.1",
1584 | "statuses": "2.0.1"
1585 | },
1586 | "engines": {
1587 | "node": ">= 0.8.0"
1588 | }
1589 | },
1590 | "node_modules/send/node_modules/encodeurl": {
1591 | "version": "1.0.2",
1592 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
1593 | "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
1594 | "license": "MIT",
1595 | "engines": {
1596 | "node": ">= 0.8"
1597 | }
1598 | },
1599 | "node_modules/send/node_modules/ms": {
1600 | "version": "2.1.3",
1601 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
1602 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
1603 | "license": "MIT"
1604 | },
1605 | "node_modules/serve-static": {
1606 | "version": "1.16.2",
1607 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
1608 | "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
1609 | "license": "MIT",
1610 | "dependencies": {
1611 | "encodeurl": "~2.0.0",
1612 | "escape-html": "~1.0.3",
1613 | "parseurl": "~1.3.3",
1614 | "send": "0.19.0"
1615 | },
1616 | "engines": {
1617 | "node": ">= 0.8.0"
1618 | }
1619 | },
1620 | "node_modules/setprototypeof": {
1621 | "version": "1.2.0",
1622 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
1623 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
1624 | "license": "ISC"
1625 | },
1626 | "node_modules/side-channel": {
1627 | "version": "1.1.0",
1628 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
1629 | "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
1630 | "license": "MIT",
1631 | "dependencies": {
1632 | "es-errors": "^1.3.0",
1633 | "object-inspect": "^1.13.3",
1634 | "side-channel-list": "^1.0.0",
1635 | "side-channel-map": "^1.0.1",
1636 | "side-channel-weakmap": "^1.0.2"
1637 | },
1638 | "engines": {
1639 | "node": ">= 0.4"
1640 | },
1641 | "funding": {
1642 | "url": "https://github.com/sponsors/ljharb"
1643 | }
1644 | },
1645 | "node_modules/side-channel-list": {
1646 | "version": "1.0.0",
1647 | "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
1648 | "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
1649 | "license": "MIT",
1650 | "dependencies": {
1651 | "es-errors": "^1.3.0",
1652 | "object-inspect": "^1.13.3"
1653 | },
1654 | "engines": {
1655 | "node": ">= 0.4"
1656 | },
1657 | "funding": {
1658 | "url": "https://github.com/sponsors/ljharb"
1659 | }
1660 | },
1661 | "node_modules/side-channel-map": {
1662 | "version": "1.0.1",
1663 | "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
1664 | "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
1665 | "license": "MIT",
1666 | "dependencies": {
1667 | "call-bound": "^1.0.2",
1668 | "es-errors": "^1.3.0",
1669 | "get-intrinsic": "^1.2.5",
1670 | "object-inspect": "^1.13.3"
1671 | },
1672 | "engines": {
1673 | "node": ">= 0.4"
1674 | },
1675 | "funding": {
1676 | "url": "https://github.com/sponsors/ljharb"
1677 | }
1678 | },
1679 | "node_modules/side-channel-weakmap": {
1680 | "version": "1.0.2",
1681 | "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
1682 | "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
1683 | "license": "MIT",
1684 | "dependencies": {
1685 | "call-bound": "^1.0.2",
1686 | "es-errors": "^1.3.0",
1687 | "get-intrinsic": "^1.2.5",
1688 | "object-inspect": "^1.13.3",
1689 | "side-channel-map": "^1.0.1"
1690 | },
1691 | "engines": {
1692 | "node": ">= 0.4"
1693 | },
1694 | "funding": {
1695 | "url": "https://github.com/sponsors/ljharb"
1696 | }
1697 | },
1698 | "node_modules/sift": {
1699 | "version": "16.0.1",
1700 | "resolved": "https://registry.npmjs.org/sift/-/sift-16.0.1.tgz",
1701 | "integrity": "sha512-Wv6BjQ5zbhW7VFefWusVP33T/EM0vYikCaQ2qR8yULbsilAT8/wQaXvuQ3ptGLpoKx+lihJE3y2UTgKDyyNHZQ==",
1702 | "license": "MIT"
1703 | },
1704 | "node_modules/simple-update-notifier": {
1705 | "version": "2.0.0",
1706 | "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
1707 | "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==",
1708 | "license": "MIT",
1709 | "dependencies": {
1710 | "semver": "^7.5.3"
1711 | },
1712 | "engines": {
1713 | "node": ">=10"
1714 | }
1715 | },
1716 | "node_modules/smart-buffer": {
1717 | "version": "4.2.0",
1718 | "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
1719 | "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
1720 | "license": "MIT",
1721 | "engines": {
1722 | "node": ">= 6.0.0",
1723 | "npm": ">= 3.0.0"
1724 | }
1725 | },
1726 | "node_modules/socks": {
1727 | "version": "2.8.4",
1728 | "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.4.tgz",
1729 | "integrity": "sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ==",
1730 | "license": "MIT",
1731 | "dependencies": {
1732 | "ip-address": "^9.0.5",
1733 | "smart-buffer": "^4.2.0"
1734 | },
1735 | "engines": {
1736 | "node": ">= 10.0.0",
1737 | "npm": ">= 3.0.0"
1738 | }
1739 | },
1740 | "node_modules/sparse-bitfield": {
1741 | "version": "3.0.3",
1742 | "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
1743 | "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==",
1744 | "license": "MIT",
1745 | "optional": true,
1746 | "dependencies": {
1747 | "memory-pager": "^1.0.2"
1748 | }
1749 | },
1750 | "node_modules/sprintf-js": {
1751 | "version": "1.1.3",
1752 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
1753 | "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==",
1754 | "license": "BSD-3-Clause"
1755 | },
1756 | "node_modules/statuses": {
1757 | "version": "2.0.1",
1758 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
1759 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
1760 | "license": "MIT",
1761 | "engines": {
1762 | "node": ">= 0.8"
1763 | }
1764 | },
1765 | "node_modules/supports-color": {
1766 | "version": "5.5.0",
1767 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
1768 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
1769 | "license": "MIT",
1770 | "dependencies": {
1771 | "has-flag": "^3.0.0"
1772 | },
1773 | "engines": {
1774 | "node": ">=4"
1775 | }
1776 | },
1777 | "node_modules/to-regex-range": {
1778 | "version": "5.0.1",
1779 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
1780 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
1781 | "license": "MIT",
1782 | "dependencies": {
1783 | "is-number": "^7.0.0"
1784 | },
1785 | "engines": {
1786 | "node": ">=8.0"
1787 | }
1788 | },
1789 | "node_modules/toidentifier": {
1790 | "version": "1.0.1",
1791 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
1792 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
1793 | "license": "MIT",
1794 | "engines": {
1795 | "node": ">=0.6"
1796 | }
1797 | },
1798 | "node_modules/touch": {
1799 | "version": "3.1.1",
1800 | "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz",
1801 | "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==",
1802 | "license": "ISC",
1803 | "bin": {
1804 | "nodetouch": "bin/nodetouch.js"
1805 | }
1806 | },
1807 | "node_modules/tr46": {
1808 | "version": "0.0.3",
1809 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
1810 | "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
1811 | "license": "MIT"
1812 | },
1813 | "node_modules/type-is": {
1814 | "version": "1.6.18",
1815 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
1816 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
1817 | "license": "MIT",
1818 | "dependencies": {
1819 | "media-typer": "0.3.0",
1820 | "mime-types": "~2.1.24"
1821 | },
1822 | "engines": {
1823 | "node": ">= 0.6"
1824 | }
1825 | },
1826 | "node_modules/undefsafe": {
1827 | "version": "2.0.5",
1828 | "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
1829 | "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
1830 | "license": "MIT"
1831 | },
1832 | "node_modules/undici-types": {
1833 | "version": "6.20.0",
1834 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
1835 | "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
1836 | "license": "MIT"
1837 | },
1838 | "node_modules/unpipe": {
1839 | "version": "1.0.0",
1840 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
1841 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
1842 | "license": "MIT",
1843 | "engines": {
1844 | "node": ">= 0.8"
1845 | }
1846 | },
1847 | "node_modules/utils-merge": {
1848 | "version": "1.0.1",
1849 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
1850 | "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
1851 | "license": "MIT",
1852 | "engines": {
1853 | "node": ">= 0.4.0"
1854 | }
1855 | },
1856 | "node_modules/uuid": {
1857 | "version": "9.0.1",
1858 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
1859 | "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
1860 | "funding": [
1861 | "https://github.com/sponsors/broofa",
1862 | "https://github.com/sponsors/ctavan"
1863 | ],
1864 | "license": "MIT",
1865 | "bin": {
1866 | "uuid": "dist/bin/uuid"
1867 | }
1868 | },
1869 | "node_modules/vary": {
1870 | "version": "1.1.2",
1871 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
1872 | "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
1873 | "license": "MIT",
1874 | "engines": {
1875 | "node": ">= 0.8"
1876 | }
1877 | },
1878 | "node_modules/webidl-conversions": {
1879 | "version": "3.0.1",
1880 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
1881 | "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
1882 | "license": "BSD-2-Clause"
1883 | },
1884 | "node_modules/whatwg-url": {
1885 | "version": "5.0.0",
1886 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
1887 | "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
1888 | "license": "MIT",
1889 | "dependencies": {
1890 | "tr46": "~0.0.3",
1891 | "webidl-conversions": "^3.0.0"
1892 | }
1893 | }
1894 | }
1895 | }
1896 |
--------------------------------------------------------------------------------
/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "server",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "type": "module",
7 | "scripts": {
8 | "start": "node index.js"
9 | },
10 | "keywords": [],
11 | "author": "",
12 | "license": "ISC",
13 | "dependencies": {
14 | "bcryptjs": "^2.4.3",
15 | "cookie-parser": "^1.4.6",
16 | "cors": "^2.8.5",
17 | "dotenv": "^16.3.1",
18 | "express": "^4.18.2",
19 | "google-auth-library": "^9.0.0",
20 | "jsonwebtoken": "^9.0.1",
21 | "mongoose": "^7.4.0",
22 | "nodemon": "^3.0.1"
23 | }
24 | }
--------------------------------------------------------------------------------
/server/routes/blogs.js:
--------------------------------------------------------------------------------
1 | import express from 'express';
2 | import { createBlog, getBlogDetails, getBlogs, getBlogsByAuthor, getBlogsBySearch, saveUnsaveBlog, getBookmarkedBlogs, getBookmarkedBlogsId, getPopularTags, getBlogsByTopic, updateBlog, deleteBlog, likeUnlikeBlog, commentBlog } from '../controllers/blogs.js';
3 |
4 | import auth from '../middleware/auth.js';
5 |
6 | const router = express.Router();
7 |
8 | router.get('/search', getBlogsBySearch);
9 | router.get('/topic/:name', getBlogsByTopic);
10 | router.get('/author/:id', auth, getBlogsByAuthor);
11 | router.get('/bookmarks', auth, getBookmarkedBlogs);
12 | router.get('/bookmarksId', auth, getBookmarkedBlogsId);
13 | router.get('/populartags', getPopularTags);
14 | router.get('/', getBlogs);
15 | router.get('/topics', getPopularTags);
16 | router.get('/:id', getBlogDetails);
17 |
18 | router.post('/bookmarks/:id', auth, saveUnsaveBlog);
19 | router.post('/commentBlog/:id', auth, commentBlog);
20 | router.get('/likeBlog/:id', auth, likeUnlikeBlog);
21 | router.post('/', auth, createBlog);
22 | router.patch('/:id', auth, updateBlog);
23 | router.delete('/:id', auth, deleteBlog);
24 |
25 | export default router;
--------------------------------------------------------------------------------
/server/routes/users.js:
--------------------------------------------------------------------------------
1 | import express from 'express';
2 |
3 | import { signin, signup, getRefreshTokens, googleSignin, signout, checkUserStatus } from '../controllers/user.js';
4 | import auth from '../middleware/auth.js';
5 |
6 | const router = express.Router();
7 |
8 | router.post('/signin', signin);
9 | router.post('/signup', signup);
10 | router.post('/google', googleSignin);
11 | router.get('/signout', signout);
12 | router.post('/google/refresh-token', getRefreshTokens);
13 | // router.get('/checkstatus', auth, checkUserStatus);
14 |
15 | export default router;
--------------------------------------------------------------------------------