├── client
├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── public
│ ├── _redirects
│ ├── favicon.ico
│ ├── images
│ │ └── 404.jpg
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── robots.txt
└── src
│ ├── App.css
│ ├── App.js
│ ├── App.test.js
│ ├── components
│ ├── CardTour.js
│ ├── DisqusThread.js
│ ├── Header.js
│ ├── LoadingToRedirect.js
│ ├── Pagination.js
│ ├── PrivateRoute.js
│ ├── RelatedTours.js
│ └── Spinner.js
│ ├── index.css
│ ├── index.js
│ ├── pages
│ ├── AddEditTour.js
│ ├── Dashboard.js
│ ├── Home.js
│ ├── Login.js
│ ├── NotFound.js
│ ├── Register.js
│ ├── SingleTour.js
│ └── TagTours.js
│ ├── redux
│ ├── api.js
│ ├── features
│ │ ├── authSlice.js
│ │ └── tourSlice.js
│ └── store.js
│ ├── reportWebVitals.js
│ ├── setupTests.js
│ └── utility
│ └── index.js
└── server
├── .gitignore
├── Procfile
├── controllers
├── tour.js
└── user.js
├── index.js
├── middleware
└── auth.js
├── models
├── tour.js
└── user.js
├── package-lock.json
├── package.json
└── routes
├── tour.js
└── user.js
/client/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/client/README.md:
--------------------------------------------------------------------------------
1 | # Getting Started with Create React App
2 |
3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
4 |
5 | ## Available Scripts
6 |
7 | In the project directory, you can run:
8 |
9 | ### `npm start`
10 |
11 | Runs the app in the development mode.\
12 | Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
13 |
14 | The page will reload when you make changes.\
15 | You may also see any lint errors in the console.
16 |
17 | ### `npm test`
18 |
19 | Launches the test runner in the interactive watch mode.\
20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
21 |
22 | ### `npm run build`
23 |
24 | Builds the app for production to the `build` folder.\
25 | It correctly bundles React in production mode and optimizes the build for the best performance.
26 |
27 | The build is minified and the filenames include the hashes.\
28 | Your app is ready to be deployed!
29 |
30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
31 |
32 | ### `npm run eject`
33 |
34 | **Note: this is a one-way operation. Once you `eject`, you can't go back!**
35 |
36 | If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
37 |
38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
39 |
40 | You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
41 |
42 | ## Learn More
43 |
44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
45 |
46 | To learn React, check out the [React documentation](https://reactjs.org/).
47 |
48 | ### Code Splitting
49 |
50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
51 |
52 | ### Analyzing the Bundle Size
53 |
54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
55 |
56 | ### Making a Progressive Web App
57 |
58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
59 |
60 | ### Advanced Configuration
61 |
62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
63 |
64 | ### Deployment
65 |
66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
67 |
68 | ### `npm run build` fails to minify
69 |
70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
71 |
--------------------------------------------------------------------------------
/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "client",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@material-ui/core": "^4.12.3",
7 | "@reduxjs/toolkit": "^1.7.1",
8 | "@testing-library/jest-dom": "^5.16.1",
9 | "@testing-library/react": "^12.1.2",
10 | "@testing-library/user-event": "^13.5.0",
11 | "axios": "^0.25.0",
12 | "jwt-decode": "^3.1.2",
13 | "material-ui-chip-input": "^1.1.0",
14 | "mdb-react-ui-kit": "^2.3.0",
15 | "moment": "^2.29.1",
16 | "prop-types": "^15.8.1",
17 | "react": "^17.0.2",
18 | "react-dom": "^17.0.2",
19 | "react-file-base64": "^1.0.3",
20 | "react-google-login": "^5.2.2",
21 | "react-redux": "^7.2.6",
22 | "react-router-dom": "^6.2.1",
23 | "react-scripts": "5.0.0",
24 | "react-toastify": "^8.1.0",
25 | "web-vitals": "^2.1.4"
26 | },
27 | "scripts": {
28 | "start": "react-scripts start",
29 | "build": "react-scripts build",
30 | "test": "react-scripts test",
31 | "eject": "react-scripts eject"
32 | },
33 | "eslintConfig": {
34 | "extends": [
35 | "react-app",
36 | "react-app/jest"
37 | ]
38 | },
39 | "browserslist": {
40 | "production": [
41 | ">0.2%",
42 | "not dead",
43 | "not op_mini all"
44 | ],
45 | "development": [
46 | "last 1 chrome version",
47 | "last 1 firefox version",
48 | "last 1 safari version"
49 | ]
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/client/public/_redirects:
--------------------------------------------------------------------------------
1 | /* /index.html 200
--------------------------------------------------------------------------------
/client/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/trickjsprogram/full-stack-mern/a70493fc852374dec0c921cb1e60d29bff16b1c3/client/public/favicon.ico
--------------------------------------------------------------------------------
/client/public/images/404.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/trickjsprogram/full-stack-mern/a70493fc852374dec0c921cb1e60d29bff16b1c3/client/public/images/404.jpg
--------------------------------------------------------------------------------
/client/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
19 |
20 |
29 | React App
30 |
31 |
32 |
33 |
34 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/client/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/trickjsprogram/full-stack-mern/a70493fc852374dec0c921cb1e60d29bff16b1c3/client/public/logo192.png
--------------------------------------------------------------------------------
/client/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/trickjsprogram/full-stack-mern/a70493fc852374dec0c921cb1e60d29bff16b1c3/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.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | height: 40vmin;
7 | pointer-events: none;
8 | }
9 |
10 | @media (prefers-reduced-motion: no-preference) {
11 | .App-logo {
12 | animation: App-logo-spin infinite 20s linear;
13 | }
14 | }
15 |
16 | .App-header {
17 | background-color: #282c34;
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | color: white;
25 | }
26 |
27 | .App-link {
28 | color: #61dafb;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from {
33 | transform: rotate(0deg);
34 | }
35 | to {
36 | transform: rotate(360deg);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/client/src/App.js:
--------------------------------------------------------------------------------
1 | import { useEffect } from "react";
2 | import "./App.css";
3 | import { ToastContainer } from "react-toastify";
4 | import { BrowserRouter, Routes, Route } from "react-router-dom";
5 | import "react-toastify/dist/ReactToastify.css";
6 | import Home from "./pages/Home";
7 | import Login from "./pages/Login";
8 | import Register from "./pages/Register";
9 | import Header from "./components/Header";
10 | import { useDispatch } from "react-redux";
11 | import { setUser } from "./redux/features/authSlice";
12 | import AddEditTour from "./pages/AddEditTour";
13 | import SingleTour from "./pages/SingleTour";
14 | import Dashboard from "./pages/Dashboard";
15 | import PrivateRoute from "./components/PrivateRoute";
16 | import NotFound from "./pages/NotFound";
17 | import TagTours from "./pages/TagTours";
18 |
19 | function App() {
20 | const dispatch = useDispatch();
21 | const user = JSON.parse(localStorage.getItem("profile"));
22 | useEffect(() => {
23 | dispatch(setUser(user));
24 | // eslint-disable-next-line react-hooks/exhaustive-deps
25 | }, []);
26 | return (
27 |
28 |
29 |
30 |
31 |
32 | } />
33 | } />
34 | } />
35 | } />
36 | } />
37 |
41 |
42 |
43 | }
44 | />
45 |
49 |
50 |
51 | }
52 | />
53 | } />
54 |
58 |
59 |
60 | }
61 | />
62 | } />
63 |
64 |
65 |
66 | );
67 | }
68 |
69 | export default App;
70 |
--------------------------------------------------------------------------------
/client/src/App.test.js:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import App from './App';
3 |
4 | test('renders learn react link', () => {
5 | render();
6 | const linkElement = screen.getByText(/learn react/i);
7 | expect(linkElement).toBeInTheDocument();
8 | });
9 |
--------------------------------------------------------------------------------
/client/src/components/CardTour.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {
3 | MDBCard,
4 | MDBCardBody,
5 | MDBCardTitle,
6 | MDBCardText,
7 | MDBCardImage,
8 | MDBCardGroup,
9 | MDBBtn,
10 | MDBIcon,
11 | MDBTooltip,
12 | } from "mdb-react-ui-kit";
13 | import { Link } from "react-router-dom";
14 | import { useSelector, useDispatch } from "react-redux";
15 | import { likeTour } from "../redux/features/tourSlice";
16 |
17 | const CardTour = ({
18 | imageFile,
19 | description,
20 | title,
21 | tags,
22 | _id,
23 | name,
24 | likes,
25 | }) => {
26 | const { user } = useSelector((state) => ({ ...state.auth }));
27 | const userId = user?.result?._id || user?.result?.googleId;
28 |
29 | const dispatch = useDispatch();
30 | const excerpt = (str) => {
31 | if (str.length > 45) {
32 | str = str.substring(0, 45) + " ...";
33 | }
34 | return str;
35 | };
36 |
37 | const Likes = () => {
38 | if (likes.length > 0) {
39 | return likes.find((like) => like === userId) ? (
40 | <>
41 |
42 |
43 | {likes.length > 2 ? (
44 |
48 | {likes.length} Likes
49 |
50 | ) : (
51 | `${likes.length} Like${likes.length > 1 ? "s" : ""}`
52 | )}
53 | >
54 | ) : (
55 | <>
56 |
57 | {likes.length} {likes.length === 1 ? "Like" : "Likes"}
58 | >
59 | );
60 | }
61 | return (
62 | <>
63 |
64 | Like
65 | >
66 | );
67 | };
68 |
69 | const handleLike = () => {
70 | dispatch(likeTour({ _id }));
71 | };
72 |
73 | return (
74 |
75 |
76 |
82 | {name}
83 |
84 | {tags.map((tag) => (
85 | #{tag}
86 | ))}
87 |
93 | {!user?.result ? (
94 |
95 |
96 |
97 | ) : (
98 |
99 | )}
100 |
101 |
102 |
103 | {title}
104 |
105 | {excerpt(description)}
106 | Read More
107 |
108 |
109 |
110 |
111 | );
112 | };
113 |
114 | export default CardTour;
115 |
--------------------------------------------------------------------------------
/client/src/components/DisqusThread.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 |
4 | function renderDisqus() {
5 | if (window.DISQUS === undefined) {
6 | var script = document.createElement("script");
7 | script.async = true;
8 | script.src =
9 | "https://" + process.env.REACT_APP_SHORTNAME + ".disqus.com/embed.js";
10 | document.getElementsByTagName("head")[0].appendChild(script);
11 | } else {
12 | window.DISQUS.reset({ reload: true });
13 | }
14 | }
15 |
16 | class DisqusThread extends React.Component {
17 | static propTypes = {
18 | id: PropTypes.string.isRequired,
19 | title: PropTypes.string.isRequired,
20 | path: PropTypes.string.isRequired,
21 | };
22 | shouldComponentUpdate(nextProps) {
23 | return (
24 | this.props.id !== nextProps.id ||
25 | this.props.title !== nextProps.title ||
26 | this.props.path !== nextProps.path
27 | );
28 | }
29 |
30 | componentDidMount() {
31 | renderDisqus();
32 | }
33 |
34 | componentDidUpdate() {
35 | renderDisqus();
36 | }
37 |
38 | render() {
39 | let { id, title, path, ...other } = this.props;
40 | console.log("this.props", this.props);
41 |
42 | if (process.env.BROWSER) {
43 | window.disqus_shortname = process.env.REACT_APP_SHORTNAME;
44 | window.disqus_identifier = id;
45 | window.disqus_title = title;
46 | window.disqus_url = process.env.REACT_APP_WEBSITE_URL + path;
47 | }
48 |
49 | return ;
50 | }
51 | }
52 |
53 | export default DisqusThread;
54 |
--------------------------------------------------------------------------------
/client/src/components/Header.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import {
3 | MDBNavbar,
4 | MDBContainer,
5 | MDBIcon,
6 | MDBNavbarNav,
7 | MDBNavbarItem,
8 | MDBNavbarLink,
9 | MDBNavbarToggler,
10 | MDBCollapse,
11 | MDBNavbarBrand,
12 | } from "mdb-react-ui-kit";
13 | import { useSelector, useDispatch } from "react-redux";
14 | import { setLogout } from "../redux/features/authSlice";
15 | import { searchTours } from "../redux/features/tourSlice";
16 | import { useNavigate } from "react-router-dom";
17 | import decode from "jwt-decode";
18 |
19 | const Header = () => {
20 | const [show, setShow] = useState(false);
21 | const [search, setSearch] = useState("");
22 | const { user } = useSelector((state) => ({ ...state.auth }));
23 | const dispatch = useDispatch();
24 | const navigate = useNavigate();
25 | const token = user?.token;
26 |
27 | if (token) {
28 | const decodedToken = decode(token);
29 | if (decodedToken.exp * 1000 < new Date().getTime()) {
30 | dispatch(setLogout());
31 | }
32 | }
33 |
34 | const handleSubmit = (e) => {
35 | e.preventDefault();
36 | if (search) {
37 | dispatch(searchTours(search));
38 | navigate(`/tours/search?searchQuery=${search}`);
39 | setSearch("");
40 | } else {
41 | navigate("/");
42 | }
43 | };
44 |
45 | const handleLogout = () => {
46 | dispatch(setLogout());
47 | };
48 |
49 | return (
50 |
51 |
52 |
56 | Touropedia
57 |
58 | setShow(!show)}
63 | style={{ color: "#606080" }}
64 | >
65 |
66 |
67 |
68 |
69 | {user?.result?._id && (
70 |
71 | Logged in as: {user?.result?.name}
72 |
73 | )}
74 |
75 |
76 | Home
77 |
78 |
79 | {user?.result?._id && (
80 | <>
81 |
82 |
83 | Add Tour
84 |
85 |
86 |
87 |
88 | Dashboard
89 |
90 |
91 | >
92 | )}
93 | {user?.result?._id ? (
94 |
95 |
96 | handleLogout()}>
97 | Logout
98 |
99 |
100 |
101 | ) : (
102 |
103 |
104 | Login
105 |
106 |
107 | )}
108 |
109 |
121 |
122 |
123 |
124 | );
125 | };
126 |
127 | export default Header;
128 |
--------------------------------------------------------------------------------
/client/src/components/LoadingToRedirect.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import { useNavigate } from "react-router-dom";
3 |
4 | const LoadingToRedirect = () => {
5 | const [count, setCount] = useState(5);
6 | const navigate = useNavigate();
7 | useEffect(() => {
8 | const interval = setInterval(() => {
9 | setCount((currentCount) => --currentCount);
10 | }, 1000);
11 |
12 | count === 0 && navigate("/login");
13 | return () => clearInterval(interval);
14 | }, [count, navigate]);
15 | return (
16 |
17 |
Redirecting you in {count} seconds
18 |
19 | );
20 | };
21 |
22 | export default LoadingToRedirect;
23 |
--------------------------------------------------------------------------------
/client/src/components/Pagination.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { MDBPagination, MDBPaginationItem, MDBBtn } from "mdb-react-ui-kit";
3 |
4 | const Pagination = ({
5 | setCurrentPage,
6 | currentPage,
7 | numberOfPages,
8 | dispatch,
9 | }) => {
10 | const renderPagination = () => {
11 | if (currentPage === numberOfPages && currentPage === 1) return null;
12 | if (currentPage === 1) {
13 | return (
14 |
15 |
16 | 1
17 |
18 |
19 | dispatch(setCurrentPage(currentPage + 1))}
23 | >
24 | Next
25 |
26 |
27 |
28 | );
29 | } else if (currentPage !== numberOfPages) {
30 | return (
31 |
32 |
33 | dispatch(setCurrentPage(currentPage - 1))}
37 | >
38 | Prev
39 |
40 |
41 |
42 | {currentPage}
43 |
44 |
45 | dispatch(setCurrentPage(currentPage + 1))}
49 | >
50 | Next
51 |
52 |
53 |
54 | );
55 | } else {
56 | return (
57 |
58 |
59 | dispatch(setCurrentPage(currentPage - 1))}
63 | >
64 | Prev
65 |
66 |
67 |
68 | {currentPage}
69 |
70 |
71 | );
72 | }
73 | };
74 |
75 | return {renderPagination()}
;
76 | };
77 |
78 | export default Pagination;
79 |
--------------------------------------------------------------------------------
/client/src/components/PrivateRoute.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useSelector } from "react-redux";
3 | import LoadingToRedirect from "./LoadingToRedirect";
4 |
5 | const PrivateRoute = ({ children }) => {
6 | const { user } = useSelector((state) => ({ ...state.auth }));
7 | return user ? children : ;
8 | };
9 |
10 | export default PrivateRoute;
11 |
--------------------------------------------------------------------------------
/client/src/components/RelatedTours.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {
3 | MDBRow,
4 | MDBCol,
5 | MDBCard,
6 | MDBCardBody,
7 | MDBCardTitle,
8 | MDBCardText,
9 | MDBCardImage,
10 | } from "mdb-react-ui-kit";
11 | import { Link } from "react-router-dom";
12 | import { excerpt } from "../utility";
13 |
14 | const RelatedTours = ({ relatedTours, tourId }) => {
15 | return (
16 | <>
17 | {relatedTours && relatedTours.length > 0 && (
18 | <>
19 | {relatedTours.length > 1 && Related Tours
}
20 |
21 | {relatedTours
22 | .filter((item) => item._id !== tourId)
23 | .splice(0, 3)
24 | .map((item) => (
25 |
26 |
27 |
28 |
33 |
34 |
35 | {item.tags.map((tag) => (
36 | #{tag}
37 | ))}
38 |
39 |
40 |
41 | {item.title}
42 |
43 |
44 | {excerpt(item.description, 45)}
45 |
46 |
47 |
48 |
49 | ))}
50 |
51 | >
52 | )}
53 | >
54 | );
55 | };
56 |
57 | export default RelatedTours;
58 |
--------------------------------------------------------------------------------
/client/src/components/Spinner.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { MDBSpinner } from "mdb-react-ui-kit";
3 |
4 | const Spinner = () => {
5 | return (
6 |
10 | Loading...
11 |
12 | );
13 | };
14 |
15 | export default Spinner;
16 |
--------------------------------------------------------------------------------
/client/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: Roboto, Helvetica, Arial, sans-serif;
4 | -webkit-font-smoothing: antialiased;
5 | -moz-osx-font-smoothing: grayscale;
6 | }
7 |
8 | code {
9 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
10 | monospace;
11 | }
12 |
13 | .header-text {
14 | color: #606080;
15 | font-weight: 500;
16 | margin-top: 17px;
17 | font-size: 17px;
18 | }
19 |
20 | .top-left {
21 | position: absolute;
22 | top: 8px;
23 | left: 16px;
24 | color: #fff;
25 | font-weight: 600;
26 | font-size: 18px;
27 | }
28 |
29 | .tag-card {
30 | margin: 10px 0 0 20px;
31 | color: #806a78;
32 | }
33 |
34 | .tourName {
35 | color: "#F48FB1";
36 | font-size: 18px;
37 | margin: 0 0 0 4px;
38 | font-weight: 500;
39 | }
40 |
41 | .tagErrMsg {
42 | color: #f93154;
43 | margin-top: 5px;
44 | text-align: left;
45 | font-size: 14px;
46 | }
47 |
48 | h4 {
49 | overflow: hidden;
50 | text-align: center;
51 | }
52 |
53 | h4:before,
54 | h4:after {
55 | background-color: #000;
56 | content: "";
57 | display: inline-block;
58 | height: 1px;
59 | position: relative;
60 | vertical-align: middle;
61 | width: 50%;
62 | }
63 |
64 | h4:before {
65 | right: 0.5em;
66 | margin-left: -50%;
67 | }
68 |
69 | h4:after {
70 | left: 0.5em;
71 | margin-right: -50%;
72 | }
--------------------------------------------------------------------------------
/client/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import "./index.css";
4 | import App from "./App";
5 | import "mdb-react-ui-kit/dist/css/mdb.min.css";
6 | import { Provider } from "react-redux";
7 | import store from "./redux/store";
8 | import reportWebVitals from "./reportWebVitals";
9 |
10 | ReactDOM.render(
11 |
12 |
13 |
14 |
15 | ,
16 | document.getElementById("root")
17 | );
18 |
19 | // If you want to start measuring performance in your app, pass a function
20 | // to log results (for example: reportWebVitals(console.log))
21 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
22 | reportWebVitals();
23 |
--------------------------------------------------------------------------------
/client/src/pages/AddEditTour.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import {
3 | MDBCard,
4 | MDBCardBody,
5 | MDBValidation,
6 | MDBBtn,
7 | MDBInput,
8 | } from "mdb-react-ui-kit";
9 | import ChipInput from "material-ui-chip-input";
10 | import FileBase from "react-file-base64";
11 | import { toast } from "react-toastify";
12 | import { useNavigate, useParams } from "react-router-dom";
13 | import { useDispatch, useSelector } from "react-redux";
14 | import { createTour, updateTour } from "../redux/features/tourSlice";
15 |
16 | const initialState = {
17 | title: "",
18 | description: "",
19 | tags: [],
20 | };
21 |
22 | const AddEditTour = () => {
23 | const [tourData, setTourData] = useState(initialState);
24 | const [tagErrMsg, setTagErrMsg] = useState(null);
25 | const { error, userTours } = useSelector((state) => ({
26 | ...state.tour,
27 | }));
28 | const { user } = useSelector((state) => ({ ...state.auth }));
29 | const dispatch = useDispatch();
30 | const navigate = useNavigate();
31 |
32 | const { title, description, tags } = tourData;
33 | const { id } = useParams();
34 |
35 | useEffect(() => {
36 | if (id) {
37 | const singleTour = userTours.find((tour) => tour._id === id);
38 | console.log(singleTour);
39 | setTourData({ ...singleTour });
40 | }
41 | // eslint-disable-next-line react-hooks/exhaustive-deps
42 | }, [id]);
43 |
44 | useEffect(() => {
45 | error && toast.error(error);
46 | }, [error]);
47 |
48 | const handleSubmit = (e) => {
49 | e.preventDefault();
50 | if (!tags.length) {
51 | setTagErrMsg("Please provide some tags");
52 | }
53 | if (title && description && tags) {
54 | const updatedTourData = { ...tourData, name: user?.result?.name };
55 |
56 | if (!id) {
57 | dispatch(createTour({ updatedTourData, navigate, toast }));
58 | } else {
59 | dispatch(updateTour({ id, updatedTourData, toast, navigate }));
60 | }
61 | handleClear();
62 | }
63 | };
64 | const onInputChange = (e) => {
65 | const { name, value } = e.target;
66 | setTourData({ ...tourData, [name]: value });
67 | };
68 | const handleAddTag = (tag) => {
69 | setTagErrMsg(null);
70 | setTourData({ ...tourData, tags: [...tourData.tags, tag] });
71 | };
72 | const handleDeleteTag = (deleteTag) => {
73 | setTourData({
74 | ...tourData,
75 | tags: tourData.tags.filter((tag) => tag !== deleteTag),
76 | });
77 | };
78 | const handleClear = () => {
79 | setTourData({ title: "", description: "", tags: [] });
80 | };
81 | return (
82 |
92 |
93 | {id ? "Update Tour" : "Add Tour"}
94 |
95 |
96 |
97 |
108 |
109 |
110 |
123 |
124 |
125 |
handleAddTag(tag)}
132 | onDelete={(tag) => handleDeleteTag(tag)}
133 | />
134 | {tagErrMsg && {tagErrMsg}
}
135 |
136 |
137 |
141 | setTourData({ ...tourData, imageFile: base64 })
142 | }
143 | />
144 |
145 |
146 |
147 | {id ? "Update" : "Submit"}
148 |
149 |
155 | Clear
156 |
157 |
158 |
159 |
160 |
161 |
162 | );
163 | };
164 |
165 | export default AddEditTour;
166 |
--------------------------------------------------------------------------------
/client/src/pages/Dashboard.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import {
3 | MDBCard,
4 | MDBCardTitle,
5 | MDBCardText,
6 | MDBCardBody,
7 | MDBCardImage,
8 | MDBRow,
9 | MDBCol,
10 | MDBBtn,
11 | MDBIcon,
12 | MDBCardGroup,
13 | } from "mdb-react-ui-kit";
14 | import { useDispatch, useSelector } from "react-redux";
15 | import { Link } from "react-router-dom";
16 | import { deleteTour, getToursByUser } from "../redux/features/tourSlice";
17 | import Spinner from "../components/Spinner";
18 | import { toast } from "react-toastify";
19 |
20 | const Dashboard = () => {
21 | const { user } = useSelector((state) => ({ ...state.auth }));
22 | const { userTours, loading } = useSelector((state) => ({ ...state.tour }));
23 | const userId = user?.result?._id;
24 | const dispatch = useDispatch();
25 |
26 | useEffect(() => {
27 | if (userId) {
28 | dispatch(getToursByUser(userId));
29 | }
30 | // eslint-disable-next-line react-hooks/exhaustive-deps
31 | }, [userId]);
32 |
33 | const excerpt = (str) => {
34 | if (str.length > 40) {
35 | str = str.substring(0, 40) + " ...";
36 | }
37 | return str;
38 | };
39 |
40 | if (loading) {
41 | return ;
42 | }
43 |
44 | const handleDelete = (id) => {
45 | if (window.confirm("Are you sure you want to delete this tour ?")) {
46 | dispatch(deleteTour({ id, toast }));
47 | }
48 | };
49 |
50 | return (
51 |
59 | {userTours.length === 0 && (
60 |
No tour available with the user: {user?.result?.name}
61 | )}
62 |
63 | {userTours.length > 0 && (
64 | <>
65 |
Dashboard: {user?.result?.name}
66 |
67 | >
68 | )}
69 |
70 | {userTours &&
71 | userTours.map((item) => (
72 |
73 |
74 |
75 |
76 |
82 |
83 |
84 |
85 |
86 | {item.title}
87 |
88 |
89 |
90 | {excerpt(item.description)}
91 |
92 |
93 |
100 |
101 | handleDelete(item._id)}
107 | />
108 |
109 |
110 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 | ))}
124 |
125 | );
126 | };
127 |
128 | export default Dashboard;
129 |
--------------------------------------------------------------------------------
/client/src/pages/Home.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import { MDBCol, MDBContainer, MDBRow, MDBTypography } from "mdb-react-ui-kit";
3 | import { useDispatch, useSelector } from "react-redux";
4 | import { getTours, setCurrentPage } from "../redux/features/tourSlice";
5 | import CardTour from "../components/CardTour";
6 | import Spinner from "../components/Spinner";
7 | import Pagination from "../components/Pagination";
8 | import { useLocation } from "react-router-dom";
9 |
10 | function useQuery() {
11 | return new URLSearchParams(useLocation().search);
12 | }
13 |
14 | const Home = () => {
15 | const { tours, loading, currentPage, numberOfPages } = useSelector(
16 | (state) => ({
17 | ...state.tour,
18 | })
19 | );
20 | const dispatch = useDispatch();
21 | const query = useQuery();
22 | const searchQuery = query.get("searchQuery");
23 | const location = useLocation();
24 |
25 | useEffect(() => {
26 | dispatch(getTours(currentPage));
27 | // eslint-disable-next-line react-hooks/exhaustive-deps
28 | }, [currentPage]);
29 |
30 | if (loading) {
31 | return ;
32 | }
33 | return (
34 |
42 |
43 | {tours.length === 0 && location.pathname === "/" && (
44 |
45 | No Tours Found
46 |
47 | )}
48 |
49 | {tours.length === 0 && location.pathname !== "/" && (
50 |
51 | We couldn't find any matches for "{searchQuery}"
52 |
53 | )}
54 |
55 |
56 |
57 |
58 | {tours &&
59 | tours.map((item) => )}
60 |
61 |
62 |
63 |
64 | {tours.length > 0 && !searchQuery && (
65 |
71 | )}
72 |
73 | );
74 | };
75 |
76 | export default Home;
77 |
--------------------------------------------------------------------------------
/client/src/pages/Login.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import {
3 | MDBCard,
4 | MDBCardBody,
5 | MDBInput,
6 | MDBCardFooter,
7 | MDBValidation,
8 | MDBBtn,
9 | MDBIcon,
10 | MDBSpinner,
11 | } from "mdb-react-ui-kit";
12 | import { Link, useNavigate } from "react-router-dom";
13 | import { useDispatch, useSelector } from "react-redux";
14 | import { toast } from "react-toastify";
15 | import { googleSignIn, login } from "../redux/features/authSlice";
16 | import { GoogleLogin } from "react-google-login";
17 |
18 | const initialState = {
19 | email: "",
20 | password: "",
21 | };
22 |
23 | const Login = () => {
24 | const [formValue, setFormValue] = useState(initialState);
25 | const { loading, error } = useSelector((state) => ({ ...state.auth }));
26 | const { email, password } = formValue;
27 | const dispatch = useDispatch();
28 | const navigate = useNavigate();
29 |
30 | useEffect(() => {
31 | error && toast.error(error);
32 | }, [error]);
33 |
34 | const handleSubmit = (e) => {
35 | e.preventDefault();
36 | if (email && password) {
37 | dispatch(login({ formValue, navigate, toast }));
38 | }
39 | };
40 | const onInputChange = (e) => {
41 | let { name, value } = e.target;
42 | setFormValue({ ...formValue, [name]: value });
43 | };
44 |
45 |
46 | const devEnv = process.env.NODE_ENV !== "production";
47 |
48 |
49 |
50 | const googleSuccess = (resp) => {
51 | const email = resp?.profileObj?.email;
52 | const name = resp?.profileObj?.name;
53 | const token = resp?.tokenId;
54 | const googleId = resp?.googleId;
55 | const result = { email, name, token, googleId };
56 | dispatch(googleSignIn({ result, navigate, toast }));
57 | };
58 | const googleFailure = (error) => {
59 | toast.error(error);
60 | };
61 | return (
62 |
71 |
72 |
73 | Sign In
74 |
75 |
76 |
77 |
87 |
88 |
89 |
99 |
100 |
101 |
102 | {loading && (
103 |
109 | )}
110 | Login
111 |
112 |
113 |
114 |
115 | (
118 |
124 | Google Sign In
125 |
126 | )}
127 | onSuccess={googleSuccess}
128 | onFailure={googleFailure}
129 | cookiePolicy="single_host_origin"
130 | />
131 |
132 |
133 |
134 | Don't have an account ? Sign Up
135 |
136 |
137 |
138 |
139 | );
140 | };
141 |
142 | export default Login;
143 |
--------------------------------------------------------------------------------
/client/src/pages/NotFound.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const NotFound = () => {
4 | return (
5 |
6 |

7 |
8 | );
9 | };
10 |
11 | export default NotFound;
12 |
--------------------------------------------------------------------------------
/client/src/pages/Register.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import {
3 | MDBCard,
4 | MDBCardBody,
5 | MDBInput,
6 | MDBCardFooter,
7 | MDBValidation,
8 | MDBBtn,
9 | MDBIcon,
10 | MDBSpinner,
11 | } from "mdb-react-ui-kit";
12 | import { Link, useNavigate } from "react-router-dom";
13 | import { useDispatch, useSelector } from "react-redux";
14 | import { toast } from "react-toastify";
15 | import { register } from "../redux/features/authSlice";
16 |
17 | const initialState = {
18 | firstName: "",
19 | lastName: "",
20 | email: "",
21 | password: "",
22 | confirmPassword: "",
23 | };
24 |
25 | const Register = () => {
26 | const [formValue, setFormValue] = useState(initialState);
27 | const { loading, error } = useSelector((state) => ({ ...state.auth }));
28 | const { email, password, firstName, lastName, confirmPassword } = formValue;
29 | const dispatch = useDispatch();
30 | const navigate = useNavigate();
31 |
32 | useEffect(() => {
33 | error && toast.error(error);
34 | }, [error]);
35 |
36 | const handleSubmit = (e) => {
37 | e.preventDefault();
38 | if (password !== confirmPassword) {
39 | return toast.error("Password should match");
40 | }
41 | if (email && password && firstName && lastName && confirmPassword) {
42 | dispatch(register({ formValue, navigate, toast }));
43 | }
44 | };
45 | const onInputChange = (e) => {
46 | let { name, value } = e.target;
47 | setFormValue({ ...formValue, [name]: value });
48 | };
49 | return (
50 |
59 |
60 |
61 | Sign Up
62 |
63 |
64 |
65 |
75 |
76 |
77 |
87 |
88 |
89 |
99 |
100 |
101 |
111 |
112 |
113 |
123 |
124 |
125 |
126 | {loading && (
127 |
133 | )}
134 | Register
135 |
136 |
137 |
138 |
139 |
140 |
141 | Already have an account ? Sign In
142 |
143 |
144 |
145 |
146 | );
147 | };
148 |
149 | export default Register;
150 |
--------------------------------------------------------------------------------
/client/src/pages/SingleTour.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import {
3 | MDBCard,
4 | MDBCardBody,
5 | MDBCardText,
6 | MDBCardImage,
7 | MDBContainer,
8 | MDBIcon,
9 | MDBBtn,
10 | } from "mdb-react-ui-kit";
11 | import { useDispatch, useSelector } from "react-redux";
12 | import { useParams, useNavigate } from "react-router-dom";
13 | import moment from "moment";
14 | import { getRelatedTours, getTour } from "../redux/features/tourSlice";
15 | import RelatedTours from "../components/RelatedTours";
16 | import DisqusThread from "../components/DisqusThread";
17 |
18 | const SingleTour = () => {
19 | const dispatch = useDispatch();
20 | const { tour, relatedTours } = useSelector((state) => ({ ...state.tour }));
21 | const { id } = useParams();
22 | const navigate = useNavigate();
23 | const tags = tour?.tags;
24 |
25 | useEffect(() => {
26 | tags && dispatch(getRelatedTours(tags));
27 | // eslint-disable-next-line react-hooks/exhaustive-deps
28 | }, [tags]);
29 |
30 | useEffect(() => {
31 | if (id) {
32 | dispatch(getTour(id));
33 | }
34 | // eslint-disable-next-line react-hooks/exhaustive-deps
35 | }, [id]);
36 | return (
37 | <>
38 |
39 |
40 |
46 |
47 | navigate("/")}
52 | >
53 |
59 |
60 | {tour.title}
61 |
62 | Created By: {tour.name}
63 |
64 |
65 |
66 | {tour && tour.tags && tour.tags.map((item) => `#${item} `)}
67 |
68 |
69 |
70 |
71 |
77 |
78 | {moment(tour.createdAt).fromNow()}
79 |
80 |
81 |
82 | {tour.description}
83 |
84 |
85 |
86 |
87 |
88 |
89 | >
90 | );
91 | };
92 |
93 | export default SingleTour;
94 |
--------------------------------------------------------------------------------
/client/src/pages/TagTours.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import {
3 | MDBCard,
4 | MDBCardTitle,
5 | MDBCardText,
6 | MDBCardBody,
7 | MDBCardImage,
8 | MDBRow,
9 | MDBCol,
10 | MDBBtn,
11 | MDBCardGroup,
12 | } from "mdb-react-ui-kit";
13 | import { useParams, useNavigate } from "react-router-dom";
14 | import Spinner from "../components/Spinner";
15 | import { useDispatch, useSelector } from "react-redux";
16 | import { getToursByTag } from "../redux/features/tourSlice";
17 | import { excerpt } from "../utility";
18 |
19 | const TagTours = () => {
20 | const { tagTours, loading } = useSelector((state) => ({ ...state.tour }));
21 | const dispatch = useDispatch();
22 | const navigate = useNavigate();
23 | const { tag } = useParams();
24 |
25 | useEffect(() => {
26 | if (tag) {
27 | dispatch(getToursByTag(tag));
28 | }
29 | // eslint-disable-next-line react-hooks/exhaustive-deps
30 | }, [tag]);
31 |
32 | if (loading) {
33 | return ;
34 | }
35 |
36 | return (
37 |
45 |
Tours with tag: {tag}
46 |
47 | {tagTours &&
48 | tagTours.map((item) => (
49 |
50 |
51 |
52 |
53 |
59 |
60 |
61 |
62 |
63 | {item.title}
64 |
65 |
66 | {excerpt(item.description, 40)}
67 |
68 |
69 | navigate(`/tour/${item._id}`)}
74 | >
75 | Read More
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 | ))}
84 |
85 | );
86 | };
87 |
88 | export default TagTours;
89 |
--------------------------------------------------------------------------------
/client/src/redux/api.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 |
3 | const devEnv = process.env.NODE_ENV !== "production";
4 |
5 | const { REACT_APP_DEV_API, REACT_APP_PROD_API } = process.env;
6 |
7 | const API = axios.create({
8 | baseURL: `${devEnv ? REACT_APP_DEV_API : REACT_APP_PROD_API}`,
9 | });
10 |
11 | API.interceptors.request.use((req) => {
12 | if (localStorage.getItem("profile")) {
13 | req.headers.Authorization = `Bearer ${
14 | JSON.parse(localStorage.getItem("profile")).token
15 | }`;
16 | }
17 | return req;
18 | });
19 |
20 | export const signIn = (formData) => API.post("/users/signin", formData);
21 | export const signUp = (formData) => API.post("/users/signup", formData);
22 | export const googleSignIn = (result) => API.post("/users/googleSignIn", result);
23 |
24 | export const createTour = (tourData) => API.post("/tour", tourData);
25 | export const getTours = (page) => API.get(`/tour?page=${page}`);
26 | export const getTour = (id) => API.get(`/tour/${id}`);
27 | export const deleteTour = (id) => API.delete(`/tour/${id}`);
28 | export const updateTour = (updatedTourData, id) =>
29 | API.patch(`/tour/${id}`, updatedTourData);
30 | export const getToursByUser = (userId) => API.get(`/tour/userTours/${userId}`);
31 |
32 | export const getToursBySearch = (searchQuery) =>
33 | API.get(`/tour/search?searchQuery=${searchQuery}`);
34 |
35 | export const getTagTours = (tag) => API.get(`/tour/tag/${tag}`);
36 | export const getRelatedTours = (tags) => API.post(`/tour/relatedTours`, tags);
37 | export const likeTour = (id) => API.patch(`/tour/like/${id}`);
38 |
--------------------------------------------------------------------------------
/client/src/redux/features/authSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
2 | import * as api from "../api";
3 |
4 | export const login = createAsyncThunk(
5 | "auth/login",
6 | async ({ formValue, navigate, toast }, { rejectWithValue }) => {
7 | try {
8 | const response = await api.signIn(formValue);
9 | toast.success("Login Successfully");
10 | navigate("/");
11 | return response.data;
12 | } catch (err) {
13 | return rejectWithValue(err.response.data);
14 | }
15 | }
16 | );
17 |
18 | export const register = createAsyncThunk(
19 | "auth/register",
20 | async ({ formValue, navigate, toast }, { rejectWithValue }) => {
21 | try {
22 | const response = await api.signUp(formValue);
23 | toast.success("Register Successfully");
24 | navigate("/");
25 | return response.data;
26 | } catch (err) {
27 | return rejectWithValue(err.response.data);
28 | }
29 | }
30 | );
31 |
32 | export const googleSignIn = createAsyncThunk(
33 | "auth/googleSignIn",
34 | async ({ result, navigate, toast }, { rejectWithValue }) => {
35 | try {
36 | const response = await api.googleSignIn(result);
37 | toast.success("Google Sign-in Successfully");
38 | navigate("/");
39 | return response.data;
40 | } catch (err) {
41 | return rejectWithValue(err.response.data);
42 | }
43 | }
44 | );
45 |
46 | const authSlice = createSlice({
47 | name: "auth",
48 | initialState: {
49 | user: null,
50 | error: "",
51 | loading: false,
52 | },
53 | reducers: {
54 | setUser: (state, action) => {
55 | state.user = action.payload;
56 | },
57 | setLogout: (state, action) => {
58 | localStorage.clear();
59 | state.user = null;
60 | },
61 | },
62 | extraReducers: {
63 | [login.pending]: (state, action) => {
64 | state.loading = true;
65 | },
66 | [login.fulfilled]: (state, action) => {
67 | state.loading = false;
68 | localStorage.setItem("profile", JSON.stringify({ ...action.payload }));
69 | state.user = action.payload;
70 | },
71 | [login.rejected]: (state, action) => {
72 | state.loading = false;
73 | state.error = action.payload.message;
74 | },
75 | [register.pending]: (state, action) => {
76 | state.loading = true;
77 | },
78 | [register.fulfilled]: (state, action) => {
79 | state.loading = false;
80 | localStorage.setItem("profile", JSON.stringify({ ...action.payload }));
81 | state.user = action.payload;
82 | },
83 | [register.rejected]: (state, action) => {
84 | state.loading = false;
85 | state.error = action.payload.message;
86 | },
87 | [googleSignIn.pending]: (state, action) => {
88 | state.loading = true;
89 | },
90 | [googleSignIn.fulfilled]: (state, action) => {
91 | state.loading = false;
92 | localStorage.setItem("profile", JSON.stringify({ ...action.payload }));
93 | state.user = action.payload;
94 | },
95 | [googleSignIn.rejected]: (state, action) => {
96 | state.loading = false;
97 | state.error = action.payload.message;
98 | },
99 | },
100 | });
101 |
102 | export const { setUser, setLogout } = authSlice.actions;
103 |
104 | export default authSlice.reducer;
105 |
--------------------------------------------------------------------------------
/client/src/redux/features/tourSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
2 | import * as api from "../api";
3 |
4 | export const createTour = createAsyncThunk(
5 | "tour/createTour",
6 | async ({ updatedTourData, navigate, toast }, { rejectWithValue }) => {
7 | try {
8 | const response = await api.createTour(updatedTourData);
9 | toast.success("Tour Added Successfully");
10 | navigate("/");
11 | return response.data;
12 | } catch (err) {
13 | return rejectWithValue(err.response.data);
14 | }
15 | }
16 | );
17 |
18 | export const getTours = createAsyncThunk(
19 | "tour/getTours",
20 | async (page, { rejectWithValue }) => {
21 | try {
22 | const response = await api.getTours(page);
23 | return response.data;
24 | } catch (err) {
25 | return rejectWithValue(err.response.data);
26 | }
27 | }
28 | );
29 |
30 | export const getTour = createAsyncThunk(
31 | "tour/getTour",
32 | async (id, { rejectWithValue }) => {
33 | try {
34 | const response = await api.getTour(id);
35 | return response.data;
36 | } catch (err) {
37 | return rejectWithValue(err.response.data);
38 | }
39 | }
40 | );
41 |
42 | export const likeTour = createAsyncThunk(
43 | "tour/likeTour",
44 | async ({ _id }, { rejectWithValue }) => {
45 | try {
46 | const response = await api.likeTour(_id);
47 | return response.data;
48 | } catch (err) {
49 | return rejectWithValue(err.response.data);
50 | }
51 | }
52 | );
53 |
54 | export const getToursByUser = createAsyncThunk(
55 | "tour/getToursByUser",
56 | async (userId, { rejectWithValue }) => {
57 | try {
58 | const response = await api.getToursByUser(userId);
59 | return response.data;
60 | } catch (err) {
61 | return rejectWithValue(err.response.data);
62 | }
63 | }
64 | );
65 |
66 | export const deleteTour = createAsyncThunk(
67 | "tour/deleteTour",
68 | async ({ id, toast }, { rejectWithValue }) => {
69 | try {
70 | const response = await api.deleteTour(id);
71 | toast.success("Tour Deleted Successfully");
72 | return response.data;
73 | } catch (err) {
74 | return rejectWithValue(err.response.data);
75 | }
76 | }
77 | );
78 |
79 | export const updateTour = createAsyncThunk(
80 | "tour/updateTour",
81 | async ({ id, updatedTourData, toast, navigate }, { rejectWithValue }) => {
82 | try {
83 | const response = await api.updateTour(updatedTourData, id);
84 | toast.success("Tour Updated Successfully");
85 | navigate("/");
86 | return response.data;
87 | } catch (err) {
88 | return rejectWithValue(err.response.data);
89 | }
90 | }
91 | );
92 |
93 | export const searchTours = createAsyncThunk(
94 | "tour/searchTours",
95 | async (searchQuery, { rejectWithValue }) => {
96 | try {
97 | const response = await api.getToursBySearch(searchQuery);
98 | return response.data;
99 | } catch (err) {
100 | return rejectWithValue(err.response.data);
101 | }
102 | }
103 | );
104 |
105 | export const getToursByTag = createAsyncThunk(
106 | "tour/getToursByTag",
107 | async (tag, { rejectWithValue }) => {
108 | try {
109 | const response = await api.getTagTours(tag);
110 | return response.data;
111 | } catch (err) {
112 | return rejectWithValue(err.response.data);
113 | }
114 | }
115 | );
116 |
117 | export const getRelatedTours = createAsyncThunk(
118 | "tour/getRelatedTours",
119 | async (tags, { rejectWithValue }) => {
120 | try {
121 | const response = await api.getRelatedTours(tags);
122 | return response.data;
123 | } catch (err) {
124 | return rejectWithValue(err.response.data);
125 | }
126 | }
127 | );
128 |
129 | const tourSlice = createSlice({
130 | name: "tour",
131 | initialState: {
132 | tour: {},
133 | tours: [],
134 | userTours: [],
135 | tagTours: [],
136 | relatedTours: [],
137 | currentPage: 1,
138 | numberOfPages: null,
139 | error: "",
140 | loading: false,
141 | },
142 | reducers: {
143 | setCurrentPage: (state, action) => {
144 | state.currentPage = action.payload;
145 | },
146 | },
147 | extraReducers: {
148 | [createTour.pending]: (state, action) => {
149 | state.loading = true;
150 | },
151 | [createTour.fulfilled]: (state, action) => {
152 | state.loading = false;
153 | state.tours = [action.payload];
154 | },
155 | [createTour.rejected]: (state, action) => {
156 | state.loading = false;
157 | state.error = action.payload.message;
158 | },
159 | [getTours.pending]: (state, action) => {
160 | state.loading = true;
161 | },
162 | [getTours.fulfilled]: (state, action) => {
163 | state.loading = false;
164 | state.tours = action.payload.data;
165 | state.numberOfPages = action.payload.numberOfPages;
166 | state.currentPage = action.payload.currentPage;
167 | },
168 | [getTours.rejected]: (state, action) => {
169 | state.loading = false;
170 | state.error = action.payload.message;
171 | },
172 | [getTour.pending]: (state, action) => {
173 | state.loading = true;
174 | },
175 | [getTour.fulfilled]: (state, action) => {
176 | state.loading = false;
177 | state.tour = action.payload;
178 | },
179 | [getTour.rejected]: (state, action) => {
180 | state.loading = false;
181 | state.error = action.payload.message;
182 | },
183 | [getToursByUser.pending]: (state, action) => {
184 | state.loading = true;
185 | },
186 | [getToursByUser.fulfilled]: (state, action) => {
187 | state.loading = false;
188 | state.userTours = action.payload;
189 | },
190 | [getToursByUser.rejected]: (state, action) => {
191 | state.loading = false;
192 | state.error = action.payload.message;
193 | },
194 | [deleteTour.pending]: (state, action) => {
195 | state.loading = true;
196 | },
197 | [deleteTour.fulfilled]: (state, action) => {
198 | state.loading = false;
199 | const {
200 | arg: { id },
201 | } = action.meta;
202 | if (id) {
203 | state.userTours = state.userTours.filter((item) => item._id !== id);
204 | state.tours = state.tours.filter((item) => item._id !== id);
205 | }
206 | },
207 | [deleteTour.rejected]: (state, action) => {
208 | state.loading = false;
209 | state.error = action.payload.message;
210 | },
211 | [updateTour.pending]: (state, action) => {
212 | state.loading = true;
213 | },
214 | [updateTour.fulfilled]: (state, action) => {
215 | state.loading = false;
216 | const {
217 | arg: { id },
218 | } = action.meta;
219 | if (id) {
220 | state.userTours = state.userTours.map((item) =>
221 | item._id === id ? action.payload : item
222 | );
223 | state.tours = state.tours.map((item) =>
224 | item._id === id ? action.payload : item
225 | );
226 | }
227 | },
228 | [updateTour.rejected]: (state, action) => {
229 | state.loading = false;
230 | state.error = action.payload.message;
231 | },
232 | [likeTour.pending]: (state, action) => {},
233 | [likeTour.fulfilled]: (state, action) => {
234 | state.loading = false;
235 | const {
236 | arg: { _id },
237 | } = action.meta;
238 | if (_id) {
239 | state.tours = state.tours.map((item) =>
240 | item._id === _id ? action.payload : item
241 | );
242 | }
243 | },
244 | [likeTour.rejected]: (state, action) => {
245 | state.error = action.payload.message;
246 | },
247 |
248 | [searchTours.pending]: (state, action) => {
249 | state.loading = true;
250 | },
251 | [searchTours.fulfilled]: (state, action) => {
252 | state.loading = false;
253 | state.tours = action.payload;
254 | },
255 | [searchTours.rejected]: (state, action) => {
256 | state.loading = false;
257 | state.error = action.payload.message;
258 | },
259 | [getToursByTag.pending]: (state, action) => {
260 | state.loading = true;
261 | },
262 | [getToursByTag.fulfilled]: (state, action) => {
263 | state.loading = false;
264 | state.tagTours = action.payload;
265 | },
266 | [getToursByTag.rejected]: (state, action) => {
267 | state.loading = false;
268 | state.error = action.payload.message;
269 | },
270 | [getRelatedTours.pending]: (state, action) => {
271 | state.loading = true;
272 | },
273 | [getRelatedTours.fulfilled]: (state, action) => {
274 | state.loading = false;
275 | state.relatedTours = action.payload;
276 | },
277 | [getRelatedTours.rejected]: (state, action) => {
278 | state.loading = false;
279 | state.error = action.payload.message;
280 | },
281 | },
282 | });
283 |
284 | export const { setCurrentPage } = tourSlice.actions;
285 |
286 | export default tourSlice.reducer;
287 |
--------------------------------------------------------------------------------
/client/src/redux/store.js:
--------------------------------------------------------------------------------
1 | import { configureStore } from "@reduxjs/toolkit";
2 | import AuthReducer from "./features/authSlice";
3 | import TourReducer from "./features/tourSlice";
4 |
5 | export default configureStore({
6 | reducer: {
7 | auth: AuthReducer,
8 | tour: TourReducer,
9 | },
10 | });
11 |
--------------------------------------------------------------------------------
/client/src/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = onPerfEntry => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 | getCLS(onPerfEntry);
5 | getFID(onPerfEntry);
6 | getFCP(onPerfEntry);
7 | getLCP(onPerfEntry);
8 | getTTFB(onPerfEntry);
9 | });
10 | }
11 | };
12 |
13 | export default reportWebVitals;
14 |
--------------------------------------------------------------------------------
/client/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/client/src/utility/index.js:
--------------------------------------------------------------------------------
1 | export const excerpt = (str, count) => {
2 | if (str.length > count) {
3 | str = str.substring(0, count) + " ...";
4 | }
5 | return str;
6 | };
7 |
--------------------------------------------------------------------------------
/server/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
--------------------------------------------------------------------------------
/server/Procfile:
--------------------------------------------------------------------------------
1 | web: npm run dev
--------------------------------------------------------------------------------
/server/controllers/tour.js:
--------------------------------------------------------------------------------
1 | import TourModal from "../models/tour.js";
2 | import mongoose from "mongoose";
3 |
4 | export const createTour = async (req, res) => {
5 | const tour = req.body;
6 | const newTour = new TourModal({
7 | ...tour,
8 | creator: req.userId,
9 | createdAt: new Date().toISOString(),
10 | });
11 |
12 | try {
13 | await newTour.save();
14 | res.status(201).json(newTour);
15 | } catch (error) {
16 | res.status(404).json({ message: "Something went wrong" });
17 | }
18 | };
19 |
20 | export const getTours = async (req, res) => {
21 | const { page } = req.query;
22 | try {
23 | // const tours = await TourModal.find();
24 | // res.status(200).json(tours);
25 |
26 | const limit = 6;
27 | const startIndex = (Number(page) - 1) * limit;
28 | const total = await TourModal.countDocuments({});
29 | const tours = await TourModal.find().limit(limit).skip(startIndex);
30 | res.json({
31 | data: tours,
32 | currentPage: Number(page),
33 | totalTours: total,
34 | numberOfPages: Math.ceil(total / limit),
35 | });
36 | } catch (error) {
37 | res.status(404).json({ message: "Something went wrong" });
38 | }
39 | };
40 |
41 | export const getTour = async (req, res) => {
42 | const { id } = req.params;
43 | try {
44 | const tour = await TourModal.findById(id);
45 | res.status(200).json(tour);
46 | } catch (error) {
47 | res.status(404).json({ message: "Something went wrong" });
48 | }
49 | };
50 |
51 | export const getToursByUser = async (req, res) => {
52 | const { id } = req.params;
53 | if (!mongoose.Types.ObjectId.isValid(id)) {
54 | return res.status(404).json({ message: "User doesn't exist" });
55 | }
56 | const userTours = await TourModal.find({ creator: id });
57 | res.status(200).json(userTours);
58 | };
59 |
60 | export const deleteTour = async (req, res) => {
61 | const { id } = req.params;
62 | try {
63 | if (!mongoose.Types.ObjectId.isValid(id)) {
64 | return res.status(404).json({ message: `No tour exist with id: ${id}` });
65 | }
66 | await TourModal.findByIdAndRemove(id);
67 | res.json({ message: "Tour deleted successfully" });
68 | } catch (error) {
69 | res.status(404).json({ message: "Something went wrong" });
70 | }
71 | };
72 |
73 | export const updateTour = async (req, res) => {
74 | const { id } = req.params;
75 | const { title, description, creator, imageFile, tags } = req.body;
76 | try {
77 | if (!mongoose.Types.ObjectId.isValid(id)) {
78 | return res.status(404).json({ message: `No tour exist with id: ${id}` });
79 | }
80 |
81 | const updatedTour = {
82 | creator,
83 | title,
84 | description,
85 | tags,
86 | imageFile,
87 | _id: id,
88 | };
89 | await TourModal.findByIdAndUpdate(id, updatedTour, { new: true });
90 | res.json(updatedTour);
91 | } catch (error) {
92 | res.status(404).json({ message: "Something went wrong" });
93 | }
94 | };
95 |
96 | export const getToursBySearch = async (req, res) => {
97 | const { searchQuery } = req.query;
98 | try {
99 | const title = new RegExp(searchQuery, "i");
100 | const tours = await TourModal.find({ title });
101 | res.json(tours);
102 | } catch (error) {
103 | res.status(404).json({ message: "Something went wrong" });
104 | }
105 | };
106 |
107 | export const getToursByTag = async (req, res) => {
108 | const { tag } = req.params;
109 | try {
110 | const tours = await TourModal.find({ tags: { $in: tag } });
111 | res.json(tours);
112 | } catch (error) {
113 | res.status(404).json({ message: "Something went wrong" });
114 | }
115 | };
116 |
117 | export const getRelatedTours = async (req, res) => {
118 | const tags = req.body;
119 | try {
120 | const tours = await TourModal.find({ tags: { $in: tags } });
121 | res.json(tours);
122 | } catch (error) {
123 | res.status(404).json({ message: "Something went wrong" });
124 | }
125 | };
126 |
127 | export const likeTour = async (req, res) => {
128 | const { id } = req.params;
129 | try {
130 | if (!req.userId) {
131 | return res.json({ message: "User is not authenticated" });
132 | }
133 |
134 | if (!mongoose.Types.ObjectId.isValid(id)) {
135 | return res.status(404).json({ message: `No tour exist with id: ${id}` });
136 | }
137 |
138 | const tour = await TourModal.findById(id);
139 |
140 | const index = tour.likes.findIndex((id) => id === String(req.userId));
141 |
142 | if (index === -1) {
143 | tour.likes.push(req.userId);
144 | } else {
145 | tour.likes = tour.likes.filter((id) => id !== String(req.userId));
146 | }
147 |
148 | const updatedTour = await TourModal.findByIdAndUpdate(id, tour, {
149 | new: true,
150 | });
151 |
152 | res.status(200).json(updatedTour);
153 | } catch (error) {
154 | res.status(404).json({ message: error.message });
155 | }
156 | };
157 |
--------------------------------------------------------------------------------
/server/controllers/user.js:
--------------------------------------------------------------------------------
1 | import bcrypt from "bcryptjs";
2 | import jwt from "jsonwebtoken";
3 |
4 | import UserModal from "../models/user.js";
5 |
6 | const secret = "test";
7 |
8 | export const signin = async (req, res) => {
9 | const { email, password } = req.body;
10 |
11 | try {
12 | const oldUser = await UserModal.findOne({ email });
13 | if (!oldUser)
14 | return res.status(404).json({ message: "User doesn't exist" });
15 |
16 | const isPasswordCorrect = await bcrypt.compare(password, oldUser.password);
17 |
18 | if (!isPasswordCorrect)
19 | return res.status(400).json({ message: "Invalid credentials" });
20 |
21 | const token = jwt.sign({ email: oldUser.email, id: oldUser._id }, secret, {
22 | expiresIn: "1h",
23 | });
24 |
25 | res.status(200).json({ result: oldUser, token });
26 | } catch (error) {
27 | res.status(500).json({ message: "Something went wrong" });
28 | console.log(error);
29 | }
30 | };
31 |
32 | export const signup = async (req, res) => {
33 | const { email, password, firstName, lastName } = req.body;
34 | try {
35 | const oldUser = await UserModal.findOne({ email });
36 |
37 | if (oldUser) {
38 | return res.status(400).json({ message: "User already exists" });
39 | }
40 |
41 | const hashedPassword = await bcrypt.hash(password, 12);
42 |
43 | const result = await UserModal.create({
44 | email,
45 | password: hashedPassword,
46 | name: `${firstName} ${lastName}`,
47 | });
48 |
49 | const token = jwt.sign({ email: result.email, id: result._id }, secret, {
50 | expiresIn: "1h",
51 | });
52 | res.status(201).json({ result, token });
53 | } catch (error) {
54 | res.status(500).json({ message: "Something went wrong" });
55 | console.log(error);
56 | }
57 | };
58 |
59 | export const googleSignIn = async (req, res) => {
60 | const { email, name, token, googleId } = req.body;
61 |
62 | try {
63 | const oldUser = await UserModal.findOne({ email });
64 | if (oldUser) {
65 | const result = { _id: oldUser._id.toString(), email, name };
66 | return res.status(200).json({ result, token });
67 | }
68 |
69 | const result = await UserModal.create({
70 | email,
71 | name,
72 | googleId,
73 | });
74 |
75 | res.status(200).json({ result, token });
76 | } catch (error) {
77 | res.status(500).json({ message: "Something went wrong" });
78 | console.log(error);
79 | }
80 | };
81 |
--------------------------------------------------------------------------------
/server/index.js:
--------------------------------------------------------------------------------
1 | import express from "express";
2 | import mongoose from "mongoose";
3 | import cors from "cors";
4 | import morgan from "morgan";
5 | import userRouter from "./routes/user.js";
6 | import tourRouter from "./routes/tour.js";
7 | import dotenv from "dotenv";
8 |
9 | const app = express();
10 | dotenv.config();
11 |
12 | app.use(morgan("dev"));
13 | app.use(express.json({ limit: "30mb", extended: true }));
14 | app.use(express.urlencoded({ limit: "30mb", extended: true }));
15 | app.use(cors());
16 |
17 | app.use("/users", userRouter); // http://localhost:5000/users/signup
18 | app.use("/tour", tourRouter);
19 | app.get("/", (req, res) => {
20 | res.send("Welcome to tour API");
21 | });
22 |
23 | const port = process.env.PORT || 5000;
24 |
25 | mongoose
26 | .connect(process.env.MONGODB_URL)
27 | .then(() => {
28 | app.listen(port, () => console.log(`Server running on port ${port}`));
29 | })
30 | .catch((error) => console.log(`${error} did not connect`));
31 |
--------------------------------------------------------------------------------
/server/middleware/auth.js:
--------------------------------------------------------------------------------
1 | import jwt from "jsonwebtoken";
2 | import UserModel from "../models/user.js";
3 |
4 | const secret = "test";
5 |
6 | const auth = async (req, res, next) => {
7 | try {
8 | const token = req.headers.authorization.split(" ")[1];
9 | const isCustomAuth = token.length < 500;
10 | let decodedData;
11 | if (token && isCustomAuth) {
12 | decodedData = jwt.verify(token, secret);
13 | req.userId = decodedData?.id;
14 | } else {
15 | decodedData = jwt.decode(token);
16 | const googleId = decodedData?.sub.toString();
17 | const user = await UserModel.findOne({ googleId });
18 | req.userId = user?._id;
19 | }
20 | next();
21 | } catch (error) {
22 | console.log(error);
23 | }
24 | };
25 |
26 | export default auth;
27 |
--------------------------------------------------------------------------------
/server/models/tour.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 |
3 | const tourSchema = mongoose.Schema({
4 | title: String,
5 | description: String,
6 | name: String,
7 | creator: String,
8 | tags: [String],
9 | imageFile: String,
10 | createdAt: {
11 | type: Date,
12 | default: new Date(),
13 | },
14 | likes: {
15 | type: [String],
16 | default: [],
17 | },
18 | });
19 |
20 | const TourModal = mongoose.model("Tour", tourSchema);
21 |
22 | export default TourModal;
23 |
--------------------------------------------------------------------------------
/server/models/user.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 |
3 | const userSchema = mongoose.Schema({
4 | name: { type: String, required: true },
5 | email: { type: String, required: true },
6 | password: { type: String, required: false },
7 | googleId: { type: String, required: false },
8 | id: { type: String },
9 | });
10 |
11 | export default mongoose.model("User", userSchema);
12 |
--------------------------------------------------------------------------------
/server/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "server",
3 | "version": "1.0.0",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "@sindresorhus/is": {
8 | "version": "0.14.0",
9 | "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz",
10 | "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==",
11 | "dev": true
12 | },
13 | "@szmarczak/http-timer": {
14 | "version": "1.1.2",
15 | "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz",
16 | "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==",
17 | "dev": true,
18 | "requires": {
19 | "defer-to-connect": "^1.0.1"
20 | }
21 | },
22 | "@types/node": {
23 | "version": "17.0.5",
24 | "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.5.tgz",
25 | "integrity": "sha512-w3mrvNXLeDYV1GKTZorGJQivK6XLCoGwpnyJFbJVK/aTBQUxOCaa/GlFAAN3OTDFcb7h5tiFG+YXCO2By+riZw=="
26 | },
27 | "@types/webidl-conversions": {
28 | "version": "6.1.1",
29 | "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-6.1.1.tgz",
30 | "integrity": "sha512-XAahCdThVuCFDQLT7R7Pk/vqeObFNL3YqRyFZg+AqAP/W1/w3xHaIxuW7WszQqTbIBOPRcItYJIou3i/mppu3Q=="
31 | },
32 | "@types/whatwg-url": {
33 | "version": "8.2.1",
34 | "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.1.tgz",
35 | "integrity": "sha512-2YubE1sjj5ifxievI5Ge1sckb9k/Er66HyR2c+3+I6VDUUg1TLPdYYTEbQ+DjRkS4nTxMJhgWfSfMRD2sl2EYQ==",
36 | "requires": {
37 | "@types/node": "*",
38 | "@types/webidl-conversions": "*"
39 | }
40 | },
41 | "abbrev": {
42 | "version": "1.1.1",
43 | "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
44 | "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
45 | "dev": true
46 | },
47 | "accepts": {
48 | "version": "1.3.7",
49 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
50 | "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
51 | "requires": {
52 | "mime-types": "~2.1.24",
53 | "negotiator": "0.6.2"
54 | }
55 | },
56 | "ansi-align": {
57 | "version": "3.0.1",
58 | "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz",
59 | "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==",
60 | "dev": true,
61 | "requires": {
62 | "string-width": "^4.1.0"
63 | }
64 | },
65 | "ansi-regex": {
66 | "version": "5.0.1",
67 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
68 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
69 | "dev": true
70 | },
71 | "ansi-styles": {
72 | "version": "4.3.0",
73 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
74 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
75 | "dev": true,
76 | "requires": {
77 | "color-convert": "^2.0.1"
78 | }
79 | },
80 | "anymatch": {
81 | "version": "3.1.2",
82 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
83 | "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
84 | "dev": true,
85 | "requires": {
86 | "normalize-path": "^3.0.0",
87 | "picomatch": "^2.0.4"
88 | }
89 | },
90 | "array-flatten": {
91 | "version": "1.1.1",
92 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
93 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
94 | },
95 | "balanced-match": {
96 | "version": "1.0.2",
97 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
98 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
99 | "dev": true
100 | },
101 | "base64-js": {
102 | "version": "1.5.1",
103 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
104 | "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
105 | },
106 | "basic-auth": {
107 | "version": "2.0.1",
108 | "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
109 | "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
110 | "requires": {
111 | "safe-buffer": "5.1.2"
112 | },
113 | "dependencies": {
114 | "safe-buffer": {
115 | "version": "5.1.2",
116 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
117 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
118 | }
119 | }
120 | },
121 | "bcryptjs": {
122 | "version": "2.4.3",
123 | "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
124 | "integrity": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms="
125 | },
126 | "binary-extensions": {
127 | "version": "2.2.0",
128 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
129 | "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
130 | "dev": true
131 | },
132 | "body-parser": {
133 | "version": "1.19.1",
134 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.1.tgz",
135 | "integrity": "sha512-8ljfQi5eBk8EJfECMrgqNGWPEY5jWP+1IzkzkGdFFEwFQZZyaZ21UqdaHktgiMlH0xLHqIFtE/u2OYE5dOtViA==",
136 | "requires": {
137 | "bytes": "3.1.1",
138 | "content-type": "~1.0.4",
139 | "debug": "2.6.9",
140 | "depd": "~1.1.2",
141 | "http-errors": "1.8.1",
142 | "iconv-lite": "0.4.24",
143 | "on-finished": "~2.3.0",
144 | "qs": "6.9.6",
145 | "raw-body": "2.4.2",
146 | "type-is": "~1.6.18"
147 | }
148 | },
149 | "boxen": {
150 | "version": "5.1.2",
151 | "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz",
152 | "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==",
153 | "dev": true,
154 | "requires": {
155 | "ansi-align": "^3.0.0",
156 | "camelcase": "^6.2.0",
157 | "chalk": "^4.1.0",
158 | "cli-boxes": "^2.2.1",
159 | "string-width": "^4.2.2",
160 | "type-fest": "^0.20.2",
161 | "widest-line": "^3.1.0",
162 | "wrap-ansi": "^7.0.0"
163 | }
164 | },
165 | "brace-expansion": {
166 | "version": "1.1.11",
167 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
168 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
169 | "dev": true,
170 | "requires": {
171 | "balanced-match": "^1.0.0",
172 | "concat-map": "0.0.1"
173 | }
174 | },
175 | "braces": {
176 | "version": "3.0.2",
177 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
178 | "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
179 | "dev": true,
180 | "requires": {
181 | "fill-range": "^7.0.1"
182 | }
183 | },
184 | "bson": {
185 | "version": "4.6.1",
186 | "resolved": "https://registry.npmjs.org/bson/-/bson-4.6.1.tgz",
187 | "integrity": "sha512-I1LQ7Hz5zgwR4QquilLNZwbhPw0Apx7i7X9kGMBTsqPdml/03Q9NBtD9nt/19ahjlphktQImrnderxqpzeVDjw==",
188 | "requires": {
189 | "buffer": "^5.6.0"
190 | }
191 | },
192 | "buffer": {
193 | "version": "5.7.1",
194 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
195 | "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
196 | "requires": {
197 | "base64-js": "^1.3.1",
198 | "ieee754": "^1.1.13"
199 | }
200 | },
201 | "buffer-equal-constant-time": {
202 | "version": "1.0.1",
203 | "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
204 | "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk="
205 | },
206 | "bytes": {
207 | "version": "3.1.1",
208 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.1.tgz",
209 | "integrity": "sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg=="
210 | },
211 | "cacheable-request": {
212 | "version": "6.1.0",
213 | "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz",
214 | "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==",
215 | "dev": true,
216 | "requires": {
217 | "clone-response": "^1.0.2",
218 | "get-stream": "^5.1.0",
219 | "http-cache-semantics": "^4.0.0",
220 | "keyv": "^3.0.0",
221 | "lowercase-keys": "^2.0.0",
222 | "normalize-url": "^4.1.0",
223 | "responselike": "^1.0.2"
224 | },
225 | "dependencies": {
226 | "get-stream": {
227 | "version": "5.2.0",
228 | "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
229 | "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
230 | "dev": true,
231 | "requires": {
232 | "pump": "^3.0.0"
233 | }
234 | },
235 | "lowercase-keys": {
236 | "version": "2.0.0",
237 | "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz",
238 | "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==",
239 | "dev": true
240 | }
241 | }
242 | },
243 | "camelcase": {
244 | "version": "6.3.0",
245 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
246 | "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
247 | "dev": true
248 | },
249 | "chalk": {
250 | "version": "4.1.2",
251 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
252 | "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
253 | "dev": true,
254 | "requires": {
255 | "ansi-styles": "^4.1.0",
256 | "supports-color": "^7.1.0"
257 | },
258 | "dependencies": {
259 | "has-flag": {
260 | "version": "4.0.0",
261 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
262 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
263 | "dev": true
264 | },
265 | "supports-color": {
266 | "version": "7.2.0",
267 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
268 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
269 | "dev": true,
270 | "requires": {
271 | "has-flag": "^4.0.0"
272 | }
273 | }
274 | }
275 | },
276 | "chokidar": {
277 | "version": "3.5.3",
278 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
279 | "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
280 | "dev": true,
281 | "requires": {
282 | "anymatch": "~3.1.2",
283 | "braces": "~3.0.2",
284 | "fsevents": "~2.3.2",
285 | "glob-parent": "~5.1.2",
286 | "is-binary-path": "~2.1.0",
287 | "is-glob": "~4.0.1",
288 | "normalize-path": "~3.0.0",
289 | "readdirp": "~3.6.0"
290 | }
291 | },
292 | "ci-info": {
293 | "version": "2.0.0",
294 | "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz",
295 | "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==",
296 | "dev": true
297 | },
298 | "cli-boxes": {
299 | "version": "2.2.1",
300 | "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz",
301 | "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==",
302 | "dev": true
303 | },
304 | "clone-response": {
305 | "version": "1.0.2",
306 | "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz",
307 | "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=",
308 | "dev": true,
309 | "requires": {
310 | "mimic-response": "^1.0.0"
311 | }
312 | },
313 | "color-convert": {
314 | "version": "2.0.1",
315 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
316 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
317 | "dev": true,
318 | "requires": {
319 | "color-name": "~1.1.4"
320 | }
321 | },
322 | "color-name": {
323 | "version": "1.1.4",
324 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
325 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
326 | "dev": true
327 | },
328 | "concat-map": {
329 | "version": "0.0.1",
330 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
331 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
332 | "dev": true
333 | },
334 | "configstore": {
335 | "version": "5.0.1",
336 | "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz",
337 | "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==",
338 | "dev": true,
339 | "requires": {
340 | "dot-prop": "^5.2.0",
341 | "graceful-fs": "^4.1.2",
342 | "make-dir": "^3.0.0",
343 | "unique-string": "^2.0.0",
344 | "write-file-atomic": "^3.0.0",
345 | "xdg-basedir": "^4.0.0"
346 | }
347 | },
348 | "content-disposition": {
349 | "version": "0.5.4",
350 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
351 | "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
352 | "requires": {
353 | "safe-buffer": "5.2.1"
354 | }
355 | },
356 | "content-type": {
357 | "version": "1.0.4",
358 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
359 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
360 | },
361 | "cookie": {
362 | "version": "0.4.1",
363 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
364 | "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA=="
365 | },
366 | "cookie-signature": {
367 | "version": "1.0.6",
368 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
369 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
370 | },
371 | "cors": {
372 | "version": "2.8.5",
373 | "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
374 | "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
375 | "requires": {
376 | "object-assign": "^4",
377 | "vary": "^1"
378 | }
379 | },
380 | "crypto-random-string": {
381 | "version": "2.0.0",
382 | "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz",
383 | "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==",
384 | "dev": true
385 | },
386 | "debug": {
387 | "version": "2.6.9",
388 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
389 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
390 | "requires": {
391 | "ms": "2.0.0"
392 | }
393 | },
394 | "decompress-response": {
395 | "version": "3.3.0",
396 | "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz",
397 | "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=",
398 | "dev": true,
399 | "requires": {
400 | "mimic-response": "^1.0.0"
401 | }
402 | },
403 | "deep-extend": {
404 | "version": "0.6.0",
405 | "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
406 | "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
407 | "dev": true
408 | },
409 | "defer-to-connect": {
410 | "version": "1.1.3",
411 | "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz",
412 | "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==",
413 | "dev": true
414 | },
415 | "denque": {
416 | "version": "2.0.1",
417 | "resolved": "https://registry.npmjs.org/denque/-/denque-2.0.1.tgz",
418 | "integrity": "sha512-tfiWc6BQLXNLpNiR5iGd0Ocu3P3VpxfzFiqubLgMfhfOw9WyvgJBd46CClNn9k3qfbjvT//0cf7AlYRX/OslMQ=="
419 | },
420 | "depd": {
421 | "version": "1.1.2",
422 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
423 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
424 | },
425 | "destroy": {
426 | "version": "1.0.4",
427 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
428 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
429 | },
430 | "dot-prop": {
431 | "version": "5.3.0",
432 | "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz",
433 | "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==",
434 | "dev": true,
435 | "requires": {
436 | "is-obj": "^2.0.0"
437 | }
438 | },
439 | "dotenv": {
440 | "version": "16.0.0",
441 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.0.tgz",
442 | "integrity": "sha512-qD9WU0MPM4SWLPJy/r2Be+2WgQj8plChsyrCNQzW/0WjvcJQiKQJ9mH3ZgB3fxbUUxgc/11ZJ0Fi5KiimWGz2Q=="
443 | },
444 | "duplexer3": {
445 | "version": "0.1.4",
446 | "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz",
447 | "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=",
448 | "dev": true
449 | },
450 | "ecdsa-sig-formatter": {
451 | "version": "1.0.11",
452 | "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
453 | "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
454 | "requires": {
455 | "safe-buffer": "^5.0.1"
456 | }
457 | },
458 | "ee-first": {
459 | "version": "1.1.1",
460 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
461 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
462 | },
463 | "emoji-regex": {
464 | "version": "8.0.0",
465 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
466 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
467 | "dev": true
468 | },
469 | "encodeurl": {
470 | "version": "1.0.2",
471 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
472 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
473 | },
474 | "end-of-stream": {
475 | "version": "1.4.4",
476 | "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
477 | "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
478 | "dev": true,
479 | "requires": {
480 | "once": "^1.4.0"
481 | }
482 | },
483 | "escape-goat": {
484 | "version": "2.1.1",
485 | "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz",
486 | "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==",
487 | "dev": true
488 | },
489 | "escape-html": {
490 | "version": "1.0.3",
491 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
492 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
493 | },
494 | "etag": {
495 | "version": "1.8.1",
496 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
497 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
498 | },
499 | "express": {
500 | "version": "4.17.2",
501 | "resolved": "https://registry.npmjs.org/express/-/express-4.17.2.tgz",
502 | "integrity": "sha512-oxlxJxcQlYwqPWKVJJtvQiwHgosH/LrLSPA+H4UxpyvSS6jC5aH+5MoHFM+KABgTOt0APue4w66Ha8jCUo9QGg==",
503 | "requires": {
504 | "accepts": "~1.3.7",
505 | "array-flatten": "1.1.1",
506 | "body-parser": "1.19.1",
507 | "content-disposition": "0.5.4",
508 | "content-type": "~1.0.4",
509 | "cookie": "0.4.1",
510 | "cookie-signature": "1.0.6",
511 | "debug": "2.6.9",
512 | "depd": "~1.1.2",
513 | "encodeurl": "~1.0.2",
514 | "escape-html": "~1.0.3",
515 | "etag": "~1.8.1",
516 | "finalhandler": "~1.1.2",
517 | "fresh": "0.5.2",
518 | "merge-descriptors": "1.0.1",
519 | "methods": "~1.1.2",
520 | "on-finished": "~2.3.0",
521 | "parseurl": "~1.3.3",
522 | "path-to-regexp": "0.1.7",
523 | "proxy-addr": "~2.0.7",
524 | "qs": "6.9.6",
525 | "range-parser": "~1.2.1",
526 | "safe-buffer": "5.2.1",
527 | "send": "0.17.2",
528 | "serve-static": "1.14.2",
529 | "setprototypeof": "1.2.0",
530 | "statuses": "~1.5.0",
531 | "type-is": "~1.6.18",
532 | "utils-merge": "1.0.1",
533 | "vary": "~1.1.2"
534 | }
535 | },
536 | "fill-range": {
537 | "version": "7.0.1",
538 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
539 | "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
540 | "dev": true,
541 | "requires": {
542 | "to-regex-range": "^5.0.1"
543 | }
544 | },
545 | "finalhandler": {
546 | "version": "1.1.2",
547 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
548 | "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
549 | "requires": {
550 | "debug": "2.6.9",
551 | "encodeurl": "~1.0.2",
552 | "escape-html": "~1.0.3",
553 | "on-finished": "~2.3.0",
554 | "parseurl": "~1.3.3",
555 | "statuses": "~1.5.0",
556 | "unpipe": "~1.0.0"
557 | }
558 | },
559 | "forwarded": {
560 | "version": "0.2.0",
561 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
562 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="
563 | },
564 | "fresh": {
565 | "version": "0.5.2",
566 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
567 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
568 | },
569 | "fsevents": {
570 | "version": "2.3.2",
571 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
572 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
573 | "dev": true,
574 | "optional": true
575 | },
576 | "get-stream": {
577 | "version": "4.1.0",
578 | "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
579 | "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
580 | "dev": true,
581 | "requires": {
582 | "pump": "^3.0.0"
583 | }
584 | },
585 | "glob-parent": {
586 | "version": "5.1.2",
587 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
588 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
589 | "dev": true,
590 | "requires": {
591 | "is-glob": "^4.0.1"
592 | }
593 | },
594 | "global-dirs": {
595 | "version": "3.0.0",
596 | "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.0.tgz",
597 | "integrity": "sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==",
598 | "dev": true,
599 | "requires": {
600 | "ini": "2.0.0"
601 | }
602 | },
603 | "got": {
604 | "version": "9.6.0",
605 | "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz",
606 | "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==",
607 | "dev": true,
608 | "requires": {
609 | "@sindresorhus/is": "^0.14.0",
610 | "@szmarczak/http-timer": "^1.1.2",
611 | "cacheable-request": "^6.0.0",
612 | "decompress-response": "^3.3.0",
613 | "duplexer3": "^0.1.4",
614 | "get-stream": "^4.1.0",
615 | "lowercase-keys": "^1.0.1",
616 | "mimic-response": "^1.0.1",
617 | "p-cancelable": "^1.0.0",
618 | "to-readable-stream": "^1.0.0",
619 | "url-parse-lax": "^3.0.0"
620 | }
621 | },
622 | "graceful-fs": {
623 | "version": "4.2.9",
624 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz",
625 | "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==",
626 | "dev": true
627 | },
628 | "has-flag": {
629 | "version": "3.0.0",
630 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
631 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
632 | "dev": true
633 | },
634 | "has-yarn": {
635 | "version": "2.1.0",
636 | "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz",
637 | "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==",
638 | "dev": true
639 | },
640 | "http-cache-semantics": {
641 | "version": "4.1.0",
642 | "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz",
643 | "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==",
644 | "dev": true
645 | },
646 | "http-errors": {
647 | "version": "1.8.1",
648 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz",
649 | "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==",
650 | "requires": {
651 | "depd": "~1.1.2",
652 | "inherits": "2.0.4",
653 | "setprototypeof": "1.2.0",
654 | "statuses": ">= 1.5.0 < 2",
655 | "toidentifier": "1.0.1"
656 | }
657 | },
658 | "iconv-lite": {
659 | "version": "0.4.24",
660 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
661 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
662 | "requires": {
663 | "safer-buffer": ">= 2.1.2 < 3"
664 | }
665 | },
666 | "ieee754": {
667 | "version": "1.2.1",
668 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
669 | "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="
670 | },
671 | "ignore-by-default": {
672 | "version": "1.0.1",
673 | "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
674 | "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=",
675 | "dev": true
676 | },
677 | "import-lazy": {
678 | "version": "2.1.0",
679 | "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz",
680 | "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=",
681 | "dev": true
682 | },
683 | "imurmurhash": {
684 | "version": "0.1.4",
685 | "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
686 | "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
687 | "dev": true
688 | },
689 | "inherits": {
690 | "version": "2.0.4",
691 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
692 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
693 | },
694 | "ini": {
695 | "version": "2.0.0",
696 | "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz",
697 | "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==",
698 | "dev": true
699 | },
700 | "ipaddr.js": {
701 | "version": "1.9.1",
702 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
703 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="
704 | },
705 | "is-binary-path": {
706 | "version": "2.1.0",
707 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
708 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
709 | "dev": true,
710 | "requires": {
711 | "binary-extensions": "^2.0.0"
712 | }
713 | },
714 | "is-ci": {
715 | "version": "2.0.0",
716 | "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz",
717 | "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==",
718 | "dev": true,
719 | "requires": {
720 | "ci-info": "^2.0.0"
721 | }
722 | },
723 | "is-extglob": {
724 | "version": "2.1.1",
725 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
726 | "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
727 | "dev": true
728 | },
729 | "is-fullwidth-code-point": {
730 | "version": "3.0.0",
731 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
732 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
733 | "dev": true
734 | },
735 | "is-glob": {
736 | "version": "4.0.3",
737 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
738 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
739 | "dev": true,
740 | "requires": {
741 | "is-extglob": "^2.1.1"
742 | }
743 | },
744 | "is-installed-globally": {
745 | "version": "0.4.0",
746 | "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz",
747 | "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==",
748 | "dev": true,
749 | "requires": {
750 | "global-dirs": "^3.0.0",
751 | "is-path-inside": "^3.0.2"
752 | }
753 | },
754 | "is-npm": {
755 | "version": "5.0.0",
756 | "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-5.0.0.tgz",
757 | "integrity": "sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA==",
758 | "dev": true
759 | },
760 | "is-number": {
761 | "version": "7.0.0",
762 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
763 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
764 | "dev": true
765 | },
766 | "is-obj": {
767 | "version": "2.0.0",
768 | "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz",
769 | "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==",
770 | "dev": true
771 | },
772 | "is-path-inside": {
773 | "version": "3.0.3",
774 | "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
775 | "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
776 | "dev": true
777 | },
778 | "is-typedarray": {
779 | "version": "1.0.0",
780 | "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
781 | "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=",
782 | "dev": true
783 | },
784 | "is-yarn-global": {
785 | "version": "0.3.0",
786 | "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz",
787 | "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==",
788 | "dev": true
789 | },
790 | "json-buffer": {
791 | "version": "3.0.0",
792 | "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz",
793 | "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=",
794 | "dev": true
795 | },
796 | "jsonwebtoken": {
797 | "version": "8.5.1",
798 | "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz",
799 | "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==",
800 | "requires": {
801 | "jws": "^3.2.2",
802 | "lodash.includes": "^4.3.0",
803 | "lodash.isboolean": "^3.0.3",
804 | "lodash.isinteger": "^4.0.4",
805 | "lodash.isnumber": "^3.0.3",
806 | "lodash.isplainobject": "^4.0.6",
807 | "lodash.isstring": "^4.0.1",
808 | "lodash.once": "^4.0.0",
809 | "ms": "^2.1.1",
810 | "semver": "^5.6.0"
811 | },
812 | "dependencies": {
813 | "ms": {
814 | "version": "2.1.3",
815 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
816 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
817 | }
818 | }
819 | },
820 | "jwa": {
821 | "version": "1.4.1",
822 | "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
823 | "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
824 | "requires": {
825 | "buffer-equal-constant-time": "1.0.1",
826 | "ecdsa-sig-formatter": "1.0.11",
827 | "safe-buffer": "^5.0.1"
828 | }
829 | },
830 | "jws": {
831 | "version": "3.2.2",
832 | "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
833 | "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
834 | "requires": {
835 | "jwa": "^1.4.1",
836 | "safe-buffer": "^5.0.1"
837 | }
838 | },
839 | "kareem": {
840 | "version": "2.3.3",
841 | "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.3.tgz",
842 | "integrity": "sha512-uESCXM2KdtOQ8LOvKyTUXEeg0MkYp4wGglTIpGcYHvjJcS5sn2Wkfrfit8m4xSbaNDAw2KdI9elgkOxZbrFYbg=="
843 | },
844 | "keyv": {
845 | "version": "3.1.0",
846 | "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz",
847 | "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==",
848 | "dev": true,
849 | "requires": {
850 | "json-buffer": "3.0.0"
851 | }
852 | },
853 | "latest-version": {
854 | "version": "5.1.0",
855 | "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz",
856 | "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==",
857 | "dev": true,
858 | "requires": {
859 | "package-json": "^6.3.0"
860 | }
861 | },
862 | "lodash.includes": {
863 | "version": "4.3.0",
864 | "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
865 | "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8="
866 | },
867 | "lodash.isboolean": {
868 | "version": "3.0.3",
869 | "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
870 | "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY="
871 | },
872 | "lodash.isinteger": {
873 | "version": "4.0.4",
874 | "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
875 | "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M="
876 | },
877 | "lodash.isnumber": {
878 | "version": "3.0.3",
879 | "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
880 | "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w="
881 | },
882 | "lodash.isplainobject": {
883 | "version": "4.0.6",
884 | "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
885 | "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs="
886 | },
887 | "lodash.isstring": {
888 | "version": "4.0.1",
889 | "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
890 | "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE="
891 | },
892 | "lodash.once": {
893 | "version": "4.1.1",
894 | "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
895 | "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w="
896 | },
897 | "lowercase-keys": {
898 | "version": "1.0.1",
899 | "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz",
900 | "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==",
901 | "dev": true
902 | },
903 | "lru-cache": {
904 | "version": "6.0.0",
905 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
906 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
907 | "dev": true,
908 | "requires": {
909 | "yallist": "^4.0.0"
910 | }
911 | },
912 | "make-dir": {
913 | "version": "3.1.0",
914 | "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
915 | "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
916 | "dev": true,
917 | "requires": {
918 | "semver": "^6.0.0"
919 | },
920 | "dependencies": {
921 | "semver": {
922 | "version": "6.3.0",
923 | "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
924 | "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
925 | "dev": true
926 | }
927 | }
928 | },
929 | "media-typer": {
930 | "version": "0.3.0",
931 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
932 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
933 | },
934 | "memory-pager": {
935 | "version": "1.5.0",
936 | "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
937 | "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==",
938 | "optional": true
939 | },
940 | "merge-descriptors": {
941 | "version": "1.0.1",
942 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
943 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
944 | },
945 | "methods": {
946 | "version": "1.1.2",
947 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
948 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
949 | },
950 | "mime": {
951 | "version": "1.6.0",
952 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
953 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
954 | },
955 | "mime-db": {
956 | "version": "1.51.0",
957 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz",
958 | "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g=="
959 | },
960 | "mime-types": {
961 | "version": "2.1.34",
962 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz",
963 | "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==",
964 | "requires": {
965 | "mime-db": "1.51.0"
966 | }
967 | },
968 | "mimic-response": {
969 | "version": "1.0.1",
970 | "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz",
971 | "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==",
972 | "dev": true
973 | },
974 | "minimatch": {
975 | "version": "3.0.4",
976 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
977 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
978 | "dev": true,
979 | "requires": {
980 | "brace-expansion": "^1.1.7"
981 | }
982 | },
983 | "minimist": {
984 | "version": "1.2.5",
985 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
986 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
987 | "dev": true
988 | },
989 | "mongodb": {
990 | "version": "4.2.2",
991 | "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.2.2.tgz",
992 | "integrity": "sha512-zt8rCTnTKyMQppyt63qMnrLM5dbADgUk18ORPF1XbtHLIYCyc9hattaYHi0pqMvNxDpgGgUofSVzS+UQErgTug==",
993 | "requires": {
994 | "bson": "^4.6.0",
995 | "denque": "^2.0.1",
996 | "mongodb-connection-string-url": "^2.3.2",
997 | "saslprep": "^1.0.3"
998 | }
999 | },
1000 | "mongodb-connection-string-url": {
1001 | "version": "2.4.1",
1002 | "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.4.1.tgz",
1003 | "integrity": "sha512-d5Kd2bVsKcSA7YI/yo57fSTtMwRQdFkvc5IZwod1RRxJtECeWPPSo7zqcUGJELifRA//Igs4spVtYAmvFCatug==",
1004 | "requires": {
1005 | "@types/whatwg-url": "^8.2.1",
1006 | "whatwg-url": "^11.0.0"
1007 | }
1008 | },
1009 | "mongoose": {
1010 | "version": "6.1.8",
1011 | "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.1.8.tgz",
1012 | "integrity": "sha512-/voqwU2dtet3zAR73r8jdPhqluU1VzIAnk7ecXPJBgyXKREnwQrz40lfW7fLpaqhmMhsAbA+JQ7ICUn2vAVFLw==",
1013 | "requires": {
1014 | "@types/node": "< 17.0.6",
1015 | "bson": "^4.2.2",
1016 | "kareem": "2.3.3",
1017 | "mongodb": "4.2.2",
1018 | "mpath": "0.8.4",
1019 | "mquery": "4.0.2",
1020 | "ms": "2.1.2",
1021 | "regexp-clone": "1.0.0",
1022 | "sift": "13.5.2"
1023 | },
1024 | "dependencies": {
1025 | "ms": {
1026 | "version": "2.1.2",
1027 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
1028 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
1029 | }
1030 | }
1031 | },
1032 | "morgan": {
1033 | "version": "1.10.0",
1034 | "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz",
1035 | "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==",
1036 | "requires": {
1037 | "basic-auth": "~2.0.1",
1038 | "debug": "2.6.9",
1039 | "depd": "~2.0.0",
1040 | "on-finished": "~2.3.0",
1041 | "on-headers": "~1.0.2"
1042 | },
1043 | "dependencies": {
1044 | "depd": {
1045 | "version": "2.0.0",
1046 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
1047 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="
1048 | }
1049 | }
1050 | },
1051 | "mpath": {
1052 | "version": "0.8.4",
1053 | "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.8.4.tgz",
1054 | "integrity": "sha512-DTxNZomBcTWlrMW76jy1wvV37X/cNNxPW1y2Jzd4DZkAaC5ZGsm8bfGfNOthcDuRJujXLqiuS6o3Tpy0JEoh7g=="
1055 | },
1056 | "mquery": {
1057 | "version": "4.0.2",
1058 | "resolved": "https://registry.npmjs.org/mquery/-/mquery-4.0.2.tgz",
1059 | "integrity": "sha512-oAVF0Nil1mT3rxty6Zln4YiD6x6QsUWYz927jZzjMxOK2aqmhEz5JQ7xmrKK7xRFA2dwV+YaOpKU/S+vfNqKxA==",
1060 | "requires": {
1061 | "debug": "4.x"
1062 | },
1063 | "dependencies": {
1064 | "debug": {
1065 | "version": "4.3.3",
1066 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz",
1067 | "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==",
1068 | "requires": {
1069 | "ms": "2.1.2"
1070 | }
1071 | },
1072 | "ms": {
1073 | "version": "2.1.2",
1074 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
1075 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
1076 | }
1077 | }
1078 | },
1079 | "ms": {
1080 | "version": "2.0.0",
1081 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
1082 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
1083 | },
1084 | "negotiator": {
1085 | "version": "0.6.2",
1086 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
1087 | "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
1088 | },
1089 | "nodemon": {
1090 | "version": "2.0.15",
1091 | "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.15.tgz",
1092 | "integrity": "sha512-gdHMNx47Gw7b3kWxJV64NI+Q5nfl0y5DgDbiVtShiwa7Z0IZ07Ll4RLFo6AjrhzMtoEZn5PDE3/c2AbVsiCkpA==",
1093 | "dev": true,
1094 | "requires": {
1095 | "chokidar": "^3.5.2",
1096 | "debug": "^3.2.7",
1097 | "ignore-by-default": "^1.0.1",
1098 | "minimatch": "^3.0.4",
1099 | "pstree.remy": "^1.1.8",
1100 | "semver": "^5.7.1",
1101 | "supports-color": "^5.5.0",
1102 | "touch": "^3.1.0",
1103 | "undefsafe": "^2.0.5",
1104 | "update-notifier": "^5.1.0"
1105 | },
1106 | "dependencies": {
1107 | "debug": {
1108 | "version": "3.2.7",
1109 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
1110 | "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
1111 | "dev": true,
1112 | "requires": {
1113 | "ms": "^2.1.1"
1114 | }
1115 | },
1116 | "ms": {
1117 | "version": "2.1.3",
1118 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
1119 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
1120 | "dev": true
1121 | }
1122 | }
1123 | },
1124 | "nopt": {
1125 | "version": "1.0.10",
1126 | "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz",
1127 | "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=",
1128 | "dev": true,
1129 | "requires": {
1130 | "abbrev": "1"
1131 | }
1132 | },
1133 | "normalize-path": {
1134 | "version": "3.0.0",
1135 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
1136 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
1137 | "dev": true
1138 | },
1139 | "normalize-url": {
1140 | "version": "4.5.1",
1141 | "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz",
1142 | "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==",
1143 | "dev": true
1144 | },
1145 | "object-assign": {
1146 | "version": "4.1.1",
1147 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
1148 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
1149 | },
1150 | "on-finished": {
1151 | "version": "2.3.0",
1152 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
1153 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
1154 | "requires": {
1155 | "ee-first": "1.1.1"
1156 | }
1157 | },
1158 | "on-headers": {
1159 | "version": "1.0.2",
1160 | "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
1161 | "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA=="
1162 | },
1163 | "once": {
1164 | "version": "1.4.0",
1165 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
1166 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
1167 | "dev": true,
1168 | "requires": {
1169 | "wrappy": "1"
1170 | }
1171 | },
1172 | "p-cancelable": {
1173 | "version": "1.1.0",
1174 | "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz",
1175 | "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==",
1176 | "dev": true
1177 | },
1178 | "package-json": {
1179 | "version": "6.5.0",
1180 | "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz",
1181 | "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==",
1182 | "dev": true,
1183 | "requires": {
1184 | "got": "^9.6.0",
1185 | "registry-auth-token": "^4.0.0",
1186 | "registry-url": "^5.0.0",
1187 | "semver": "^6.2.0"
1188 | },
1189 | "dependencies": {
1190 | "semver": {
1191 | "version": "6.3.0",
1192 | "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
1193 | "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
1194 | "dev": true
1195 | }
1196 | }
1197 | },
1198 | "parseurl": {
1199 | "version": "1.3.3",
1200 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
1201 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
1202 | },
1203 | "path-to-regexp": {
1204 | "version": "0.1.7",
1205 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
1206 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
1207 | },
1208 | "picomatch": {
1209 | "version": "2.3.1",
1210 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
1211 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
1212 | "dev": true
1213 | },
1214 | "prepend-http": {
1215 | "version": "2.0.0",
1216 | "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz",
1217 | "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=",
1218 | "dev": true
1219 | },
1220 | "proxy-addr": {
1221 | "version": "2.0.7",
1222 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
1223 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
1224 | "requires": {
1225 | "forwarded": "0.2.0",
1226 | "ipaddr.js": "1.9.1"
1227 | }
1228 | },
1229 | "pstree.remy": {
1230 | "version": "1.1.8",
1231 | "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
1232 | "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==",
1233 | "dev": true
1234 | },
1235 | "pump": {
1236 | "version": "3.0.0",
1237 | "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
1238 | "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
1239 | "dev": true,
1240 | "requires": {
1241 | "end-of-stream": "^1.1.0",
1242 | "once": "^1.3.1"
1243 | }
1244 | },
1245 | "punycode": {
1246 | "version": "2.1.1",
1247 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
1248 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
1249 | },
1250 | "pupa": {
1251 | "version": "2.1.1",
1252 | "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz",
1253 | "integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==",
1254 | "dev": true,
1255 | "requires": {
1256 | "escape-goat": "^2.0.0"
1257 | }
1258 | },
1259 | "qs": {
1260 | "version": "6.9.6",
1261 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.6.tgz",
1262 | "integrity": "sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ=="
1263 | },
1264 | "range-parser": {
1265 | "version": "1.2.1",
1266 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
1267 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
1268 | },
1269 | "raw-body": {
1270 | "version": "2.4.2",
1271 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.2.tgz",
1272 | "integrity": "sha512-RPMAFUJP19WIet/99ngh6Iv8fzAbqum4Li7AD6DtGaW2RpMB/11xDoalPiJMTbu6I3hkbMVkATvZrqb9EEqeeQ==",
1273 | "requires": {
1274 | "bytes": "3.1.1",
1275 | "http-errors": "1.8.1",
1276 | "iconv-lite": "0.4.24",
1277 | "unpipe": "1.0.0"
1278 | }
1279 | },
1280 | "rc": {
1281 | "version": "1.2.8",
1282 | "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
1283 | "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
1284 | "dev": true,
1285 | "requires": {
1286 | "deep-extend": "^0.6.0",
1287 | "ini": "~1.3.0",
1288 | "minimist": "^1.2.0",
1289 | "strip-json-comments": "~2.0.1"
1290 | },
1291 | "dependencies": {
1292 | "ini": {
1293 | "version": "1.3.8",
1294 | "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
1295 | "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
1296 | "dev": true
1297 | }
1298 | }
1299 | },
1300 | "readdirp": {
1301 | "version": "3.6.0",
1302 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
1303 | "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
1304 | "dev": true,
1305 | "requires": {
1306 | "picomatch": "^2.2.1"
1307 | }
1308 | },
1309 | "regexp-clone": {
1310 | "version": "1.0.0",
1311 | "resolved": "https://registry.npmjs.org/regexp-clone/-/regexp-clone-1.0.0.tgz",
1312 | "integrity": "sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw=="
1313 | },
1314 | "registry-auth-token": {
1315 | "version": "4.2.1",
1316 | "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz",
1317 | "integrity": "sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw==",
1318 | "dev": true,
1319 | "requires": {
1320 | "rc": "^1.2.8"
1321 | }
1322 | },
1323 | "registry-url": {
1324 | "version": "5.1.0",
1325 | "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz",
1326 | "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==",
1327 | "dev": true,
1328 | "requires": {
1329 | "rc": "^1.2.8"
1330 | }
1331 | },
1332 | "responselike": {
1333 | "version": "1.0.2",
1334 | "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz",
1335 | "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=",
1336 | "dev": true,
1337 | "requires": {
1338 | "lowercase-keys": "^1.0.0"
1339 | }
1340 | },
1341 | "safe-buffer": {
1342 | "version": "5.2.1",
1343 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
1344 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
1345 | },
1346 | "safer-buffer": {
1347 | "version": "2.1.2",
1348 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
1349 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
1350 | },
1351 | "saslprep": {
1352 | "version": "1.0.3",
1353 | "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz",
1354 | "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==",
1355 | "optional": true,
1356 | "requires": {
1357 | "sparse-bitfield": "^3.0.3"
1358 | }
1359 | },
1360 | "semver": {
1361 | "version": "5.7.1",
1362 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
1363 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
1364 | },
1365 | "semver-diff": {
1366 | "version": "3.1.1",
1367 | "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz",
1368 | "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==",
1369 | "dev": true,
1370 | "requires": {
1371 | "semver": "^6.3.0"
1372 | },
1373 | "dependencies": {
1374 | "semver": {
1375 | "version": "6.3.0",
1376 | "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
1377 | "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
1378 | "dev": true
1379 | }
1380 | }
1381 | },
1382 | "send": {
1383 | "version": "0.17.2",
1384 | "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz",
1385 | "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==",
1386 | "requires": {
1387 | "debug": "2.6.9",
1388 | "depd": "~1.1.2",
1389 | "destroy": "~1.0.4",
1390 | "encodeurl": "~1.0.2",
1391 | "escape-html": "~1.0.3",
1392 | "etag": "~1.8.1",
1393 | "fresh": "0.5.2",
1394 | "http-errors": "1.8.1",
1395 | "mime": "1.6.0",
1396 | "ms": "2.1.3",
1397 | "on-finished": "~2.3.0",
1398 | "range-parser": "~1.2.1",
1399 | "statuses": "~1.5.0"
1400 | },
1401 | "dependencies": {
1402 | "ms": {
1403 | "version": "2.1.3",
1404 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
1405 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
1406 | }
1407 | }
1408 | },
1409 | "serve-static": {
1410 | "version": "1.14.2",
1411 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz",
1412 | "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==",
1413 | "requires": {
1414 | "encodeurl": "~1.0.2",
1415 | "escape-html": "~1.0.3",
1416 | "parseurl": "~1.3.3",
1417 | "send": "0.17.2"
1418 | }
1419 | },
1420 | "setprototypeof": {
1421 | "version": "1.2.0",
1422 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
1423 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
1424 | },
1425 | "sift": {
1426 | "version": "13.5.2",
1427 | "resolved": "https://registry.npmjs.org/sift/-/sift-13.5.2.tgz",
1428 | "integrity": "sha512-+gxdEOMA2J+AI+fVsCqeNn7Tgx3M9ZN9jdi95939l1IJ8cZsqS8sqpJyOkic2SJk+1+98Uwryt/gL6XDaV+UZA=="
1429 | },
1430 | "signal-exit": {
1431 | "version": "3.0.6",
1432 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.6.tgz",
1433 | "integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==",
1434 | "dev": true
1435 | },
1436 | "sparse-bitfield": {
1437 | "version": "3.0.3",
1438 | "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
1439 | "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=",
1440 | "optional": true,
1441 | "requires": {
1442 | "memory-pager": "^1.0.2"
1443 | }
1444 | },
1445 | "statuses": {
1446 | "version": "1.5.0",
1447 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
1448 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
1449 | },
1450 | "string-width": {
1451 | "version": "4.2.3",
1452 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
1453 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
1454 | "dev": true,
1455 | "requires": {
1456 | "emoji-regex": "^8.0.0",
1457 | "is-fullwidth-code-point": "^3.0.0",
1458 | "strip-ansi": "^6.0.1"
1459 | }
1460 | },
1461 | "strip-ansi": {
1462 | "version": "6.0.1",
1463 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
1464 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
1465 | "dev": true,
1466 | "requires": {
1467 | "ansi-regex": "^5.0.1"
1468 | }
1469 | },
1470 | "strip-json-comments": {
1471 | "version": "2.0.1",
1472 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
1473 | "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
1474 | "dev": true
1475 | },
1476 | "supports-color": {
1477 | "version": "5.5.0",
1478 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
1479 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
1480 | "dev": true,
1481 | "requires": {
1482 | "has-flag": "^3.0.0"
1483 | }
1484 | },
1485 | "to-readable-stream": {
1486 | "version": "1.0.0",
1487 | "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz",
1488 | "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==",
1489 | "dev": true
1490 | },
1491 | "to-regex-range": {
1492 | "version": "5.0.1",
1493 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
1494 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
1495 | "dev": true,
1496 | "requires": {
1497 | "is-number": "^7.0.0"
1498 | }
1499 | },
1500 | "toidentifier": {
1501 | "version": "1.0.1",
1502 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
1503 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="
1504 | },
1505 | "touch": {
1506 | "version": "3.1.0",
1507 | "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz",
1508 | "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==",
1509 | "dev": true,
1510 | "requires": {
1511 | "nopt": "~1.0.10"
1512 | }
1513 | },
1514 | "tr46": {
1515 | "version": "3.0.0",
1516 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz",
1517 | "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==",
1518 | "requires": {
1519 | "punycode": "^2.1.1"
1520 | }
1521 | },
1522 | "type-fest": {
1523 | "version": "0.20.2",
1524 | "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
1525 | "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
1526 | "dev": true
1527 | },
1528 | "type-is": {
1529 | "version": "1.6.18",
1530 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
1531 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
1532 | "requires": {
1533 | "media-typer": "0.3.0",
1534 | "mime-types": "~2.1.24"
1535 | }
1536 | },
1537 | "typedarray-to-buffer": {
1538 | "version": "3.1.5",
1539 | "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
1540 | "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==",
1541 | "dev": true,
1542 | "requires": {
1543 | "is-typedarray": "^1.0.0"
1544 | }
1545 | },
1546 | "undefsafe": {
1547 | "version": "2.0.5",
1548 | "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
1549 | "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
1550 | "dev": true
1551 | },
1552 | "unique-string": {
1553 | "version": "2.0.0",
1554 | "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz",
1555 | "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==",
1556 | "dev": true,
1557 | "requires": {
1558 | "crypto-random-string": "^2.0.0"
1559 | }
1560 | },
1561 | "unpipe": {
1562 | "version": "1.0.0",
1563 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
1564 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
1565 | },
1566 | "update-notifier": {
1567 | "version": "5.1.0",
1568 | "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-5.1.0.tgz",
1569 | "integrity": "sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw==",
1570 | "dev": true,
1571 | "requires": {
1572 | "boxen": "^5.0.0",
1573 | "chalk": "^4.1.0",
1574 | "configstore": "^5.0.1",
1575 | "has-yarn": "^2.1.0",
1576 | "import-lazy": "^2.1.0",
1577 | "is-ci": "^2.0.0",
1578 | "is-installed-globally": "^0.4.0",
1579 | "is-npm": "^5.0.0",
1580 | "is-yarn-global": "^0.3.0",
1581 | "latest-version": "^5.1.0",
1582 | "pupa": "^2.1.1",
1583 | "semver": "^7.3.4",
1584 | "semver-diff": "^3.1.1",
1585 | "xdg-basedir": "^4.0.0"
1586 | },
1587 | "dependencies": {
1588 | "semver": {
1589 | "version": "7.3.5",
1590 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
1591 | "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
1592 | "dev": true,
1593 | "requires": {
1594 | "lru-cache": "^6.0.0"
1595 | }
1596 | }
1597 | }
1598 | },
1599 | "url-parse-lax": {
1600 | "version": "3.0.0",
1601 | "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz",
1602 | "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=",
1603 | "dev": true,
1604 | "requires": {
1605 | "prepend-http": "^2.0.0"
1606 | }
1607 | },
1608 | "utils-merge": {
1609 | "version": "1.0.1",
1610 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
1611 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
1612 | },
1613 | "vary": {
1614 | "version": "1.1.2",
1615 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
1616 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
1617 | },
1618 | "webidl-conversions": {
1619 | "version": "7.0.0",
1620 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
1621 | "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g=="
1622 | },
1623 | "whatwg-url": {
1624 | "version": "11.0.0",
1625 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz",
1626 | "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==",
1627 | "requires": {
1628 | "tr46": "^3.0.0",
1629 | "webidl-conversions": "^7.0.0"
1630 | }
1631 | },
1632 | "widest-line": {
1633 | "version": "3.1.0",
1634 | "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz",
1635 | "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==",
1636 | "dev": true,
1637 | "requires": {
1638 | "string-width": "^4.0.0"
1639 | }
1640 | },
1641 | "wrap-ansi": {
1642 | "version": "7.0.0",
1643 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
1644 | "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
1645 | "dev": true,
1646 | "requires": {
1647 | "ansi-styles": "^4.0.0",
1648 | "string-width": "^4.1.0",
1649 | "strip-ansi": "^6.0.0"
1650 | }
1651 | },
1652 | "wrappy": {
1653 | "version": "1.0.2",
1654 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
1655 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
1656 | "dev": true
1657 | },
1658 | "write-file-atomic": {
1659 | "version": "3.0.3",
1660 | "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz",
1661 | "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==",
1662 | "dev": true,
1663 | "requires": {
1664 | "imurmurhash": "^0.1.4",
1665 | "is-typedarray": "^1.0.0",
1666 | "signal-exit": "^3.0.2",
1667 | "typedarray-to-buffer": "^3.1.5"
1668 | }
1669 | },
1670 | "xdg-basedir": {
1671 | "version": "4.0.0",
1672 | "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz",
1673 | "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==",
1674 | "dev": true
1675 | },
1676 | "yallist": {
1677 | "version": "4.0.0",
1678 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
1679 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
1680 | "dev": true
1681 | }
1682 | }
1683 | }
1684 |
--------------------------------------------------------------------------------
/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "server",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "type": "module",
7 | "scripts": {
8 | "test": "echo \"Error: no test specified\" && exit 1",
9 | "dev": "nodemon index.js"
10 | },
11 | "author": "",
12 | "license": "ISC",
13 | "dependencies": {
14 | "bcryptjs": "^2.4.3",
15 | "cors": "^2.8.5",
16 | "dotenv": "^16.0.0",
17 | "express": "^4.17.2",
18 | "jsonwebtoken": "^8.5.1",
19 | "mongoose": "^6.1.8",
20 | "morgan": "^1.10.0"
21 | },
22 | "devDependencies": {
23 | "nodemon": "^2.0.15"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/server/routes/tour.js:
--------------------------------------------------------------------------------
1 | import express from "express";
2 | const router = express.Router();
3 | import auth from "../middleware/auth.js";
4 |
5 | import {
6 | createTour,
7 | deleteTour,
8 | getRelatedTours,
9 | getTour,
10 | getTours,
11 | getToursBySearch,
12 | getToursByTag,
13 | getToursByUser,
14 | likeTour,
15 | updateTour,
16 | } from "../controllers/tour.js";
17 |
18 | router.get("/search", getToursBySearch);
19 | router.get("/tag/:tag", getToursByTag);
20 | router.post("/relatedTours", getRelatedTours);
21 | router.get("/", getTours);
22 | router.get("/:id", getTour);
23 |
24 | router.post("/", auth, createTour);
25 | router.delete("/:id", auth, deleteTour);
26 | router.patch("/:id", auth, updateTour);
27 | router.get("/userTours/:id", auth, getToursByUser);
28 | router.patch("/like/:id", auth, likeTour);
29 |
30 | export default router;
31 |
--------------------------------------------------------------------------------
/server/routes/user.js:
--------------------------------------------------------------------------------
1 | import express from "express";
2 | const router = express.Router();
3 |
4 | import { signup, signin, googleSignIn } from "../controllers/user.js";
5 |
6 | router.post("/signup", signup);
7 | router.post("/signin", signin);
8 | router.post("/googleSignIn", googleSignIn);
9 |
10 | export default router;
11 |
--------------------------------------------------------------------------------