├── app ├── .env.example ├── src │ ├── .env.example │ ├── react-app-env.d.ts │ ├── pages │ │ ├── Login │ │ │ ├── index.tsx │ │ │ └── Login.tsx │ │ ├── Register │ │ │ ├── index.tsx │ │ │ └── Register.tsx │ │ ├── NotFound │ │ │ └── index.tsx │ │ └── Contacts │ │ │ ├── UploadProfile.tsx │ │ │ ├── ContactForm.tsx │ │ │ └── index.tsx │ ├── asset │ │ └── cm.png │ ├── components │ │ ├── layout │ │ │ ├── index.tsx │ │ │ ├── AppLayout.tsx │ │ │ └── Profile.tsx │ │ ├── Custom │ │ │ ├── CSearch │ │ │ │ └── index.tsx │ │ │ ├── CSpin │ │ │ │ └── index.tsx │ │ │ ├── index.tsx │ │ │ ├── CInput │ │ │ │ └── index.tsx │ │ │ ├── CTitle │ │ │ │ └── index.tsx │ │ │ ├── CInputPassword │ │ │ │ └── index.tsx │ │ │ ├── CButton │ │ │ │ └── index.tsx │ │ │ ├── CInputNumber │ │ │ │ └── index.tsx │ │ │ ├── CFormItem │ │ │ │ └── index.tsx │ │ │ └── CTable │ │ │ │ └── index.tsx │ │ └── TopHeader │ │ │ └── index.tsx │ ├── utils │ │ ├── getFirstLetter.ts │ │ ├── reduxHooks │ │ │ └── index.tsx │ │ └── axios │ │ │ └── index.tsx │ ├── App.tsx │ ├── redux │ │ ├── types │ │ │ ├── index.ts │ │ │ ├── usersTypes.ts │ │ │ └── contactsTypes.ts │ │ ├── sagas │ │ │ ├── rootSaga.tsx │ │ │ ├── usersSaga.ts │ │ │ └── contactsSaga.ts │ │ ├── reducers │ │ │ ├── index.ts │ │ │ ├── contactsReducer.ts │ │ │ └── usersReducer.ts │ │ ├── store.tsx │ │ └── actions │ │ │ ├── userAction.ts │ │ │ └── contactsAction.ts │ ├── setupTests.ts │ ├── App.test.tsx │ ├── constants │ │ └── Color.ts │ ├── reportWebVitals.ts │ ├── routes │ │ ├── routes.ts │ │ └── index.tsx │ ├── App.css │ ├── index.tsx │ ├── interfaces │ │ └── index.ts │ └── logo.svg ├── public │ ├── robots.txt │ ├── favicon.ico │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── index.html ├── .gitignore ├── tsconfig.json ├── package.json └── README.md ├── server ├── .gitignore ├── index.js ├── model │ ├── user.js │ └── contact.js ├── config │ └── database.js ├── package.json ├── middleware │ └── auth.js ├── app.js └── yarn.lock ├── .github └── workflows │ └── node.js.yml └── README.md /app/.env.example: -------------------------------------------------------------------------------- 1 | REACT_APP_API_HOST = 'http://localhost:4001/' -------------------------------------------------------------------------------- /app/src/.env.example: -------------------------------------------------------------------------------- 1 | REACT_APP_API_HOST = 'http://localhost:4001/' -------------------------------------------------------------------------------- /app/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /app/src/pages/Login/index.tsx: -------------------------------------------------------------------------------- 1 | import Login from "./Login"; 2 | 3 | export { Login }; 4 | -------------------------------------------------------------------------------- /app/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /app/src/pages/Register/index.tsx: -------------------------------------------------------------------------------- 1 | import Register from "./Register"; 2 | 3 | export { Register }; 4 | -------------------------------------------------------------------------------- /app/src/asset/cm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudiprajkunwar/contact-manager/HEAD/app/src/asset/cm.png -------------------------------------------------------------------------------- /app/src/components/layout/index.tsx: -------------------------------------------------------------------------------- 1 | import AppLayout from "./AppLayout"; 2 | 3 | export { AppLayout }; 4 | -------------------------------------------------------------------------------- /app/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudiprajkunwar/contact-manager/HEAD/app/public/favicon.ico -------------------------------------------------------------------------------- /app/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudiprajkunwar/contact-manager/HEAD/app/public/logo192.png -------------------------------------------------------------------------------- /app/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudiprajkunwar/contact-manager/HEAD/app/public/logo512.png -------------------------------------------------------------------------------- /app/src/utils/getFirstLetter.ts: -------------------------------------------------------------------------------- 1 | export const getFirstLetter = (data: string) => { 2 | if (!data) { 3 | return; 4 | } 5 | return data.charAt(0).toUpperCase(); 6 | }; 7 | -------------------------------------------------------------------------------- /app/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "antd/dist/antd.css"; 3 | import Routes from "./routes"; 4 | 5 | function App() { 6 | return ; 7 | } 8 | 9 | export default App; 10 | -------------------------------------------------------------------------------- /app/src/redux/types/index.ts: -------------------------------------------------------------------------------- 1 | import { contacts } from "./contactsTypes"; 2 | import { users } from "./usersTypes"; 3 | 4 | const types = { 5 | ...users, 6 | ...contacts, 7 | }; 8 | 9 | export default types; 10 | -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | # Include your project-specific ignores in this file 2 | # Read about how to use .gitignore: https://help.github.com/articles/ignoring-files 3 | 4 | # Dependency directories 5 | node_modules 6 | 7 | # Dotenv configuration 8 | .env 9 | -------------------------------------------------------------------------------- /app/src/redux/sagas/rootSaga.tsx: -------------------------------------------------------------------------------- 1 | import { all } from "redux-saga/effects"; 2 | import { contactsSaga } from "./contactsSaga"; 3 | 4 | import { usersSaga } from "./usersSaga"; 5 | 6 | export default function* RootSaga() { 7 | yield all([usersSaga(), contactsSaga()]); 8 | } 9 | -------------------------------------------------------------------------------- /app/src/setupTests.ts: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /app/src/redux/reducers/index.ts: -------------------------------------------------------------------------------- 1 | import { combineReducers } from "redux"; 2 | import { usersReducer } from "./usersReducer"; 3 | import { contactsReducer } from "./contactsReducer"; 4 | 5 | export const RootReducer = combineReducers({ 6 | users: usersReducer, 7 | contacts: contactsReducer, 8 | }); 9 | -------------------------------------------------------------------------------- /app/src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render, screen } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | render(); 7 | const linkElement = screen.getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | const http = require("http"); 2 | const app = require("./app"); 3 | const server = http.createServer(app); 4 | 5 | const { API_PORT } = process.env; 6 | const port = process.env.PORT || API_PORT; 7 | 8 | // server listening 9 | server.listen(port, () => { 10 | console.log(`Server running on port ${port}`); 11 | }); 12 | -------------------------------------------------------------------------------- /app/src/components/Custom/CSearch/index.tsx: -------------------------------------------------------------------------------- 1 | import { Input } from "antd"; 2 | import * as React from "react"; 3 | import { SearchProps } from "antd/es/input"; 4 | 5 | const { Search } = Input; 6 | 7 | type CSearchProps = SearchProps; 8 | 9 | export const CSearch: React.FC = (props) => { 10 | return ; 11 | }; 12 | -------------------------------------------------------------------------------- /app/src/constants/Color.ts: -------------------------------------------------------------------------------- 1 | export interface ColorProps { 2 | primary: string; 3 | lightBlue: string; 4 | textDark: string; 5 | textLight: string; 6 | textGrey: string; 7 | } 8 | 9 | export const Color: ColorProps = { 10 | primary: "#0257FF", 11 | lightBlue: "#3B4CB8", 12 | textDark: "#2E2E2E", 13 | textLight: "#f9fcff", 14 | textGrey: "#595959", 15 | }; 16 | -------------------------------------------------------------------------------- /app/src/components/Custom/CSpin/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Spin, SpinProps } from "antd"; 3 | import styled from "@emotion/styled"; 4 | 5 | const Spinner = styled(Spin)` 6 | position: absolute; 7 | top: 50%; 8 | left: 50%; 9 | transform: translate(-50%, -50%); 10 | `; 11 | export const CSpin = (props: SpinProps) => { 12 | return ; 13 | }; 14 | -------------------------------------------------------------------------------- /app/src/utils/reduxHooks/index.tsx: -------------------------------------------------------------------------------- 1 | import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux"; 2 | import { AppDispatch, RootState } from "../../redux/store"; 3 | 4 | // Use throughout your app instead of plain `useDispatch` and `useSelector` 5 | export const useAppDispatch = () => useDispatch(); 6 | export const useAppSelector: TypedUseSelectorHook = useSelector; 7 | -------------------------------------------------------------------------------- /app/src/components/Custom/index.tsx: -------------------------------------------------------------------------------- 1 | export { CButton } from "./CButton"; 2 | export { CFormItem } from "./CFormItem"; 3 | export { CInput } from "./CInput"; 4 | export { CInputPassword } from "./CInputPassword"; 5 | export { CTable } from "./CTable"; 6 | export { CTitle } from "./CTitle"; 7 | export { CSpin } from "./CSpin"; 8 | export { CSearch } from "./CSearch"; 9 | export { CInputNumber } from "./CInputNumber"; 10 | -------------------------------------------------------------------------------- /server/model/user.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | const Schema = mongoose.Schema; 3 | 4 | const userSchema = new Schema( 5 | { 6 | full_name: { type: String, default: null }, 7 | email: { type: String, unique: true }, 8 | password: { type: String }, 9 | token: { type: String }, 10 | }, 11 | { versionKey: false } 12 | ); 13 | 14 | module.exports = mongoose.model("user", userSchema); 15 | -------------------------------------------------------------------------------- /app/.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 | .env 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 | -------------------------------------------------------------------------------- /app/src/redux/types/usersTypes.ts: -------------------------------------------------------------------------------- 1 | export const users = { 2 | SIGNIN_USER: "SIGNIN_USER", 3 | SIGNIN_USER_SUCCESS: "SIGNIN_USER_SUCCESS", 4 | 5 | SIGNUP_USER: "SIGNUP_USER", 6 | SIGNUP_USER_SUCCESS: "SIGNUP_USER_SUCCESS", 7 | 8 | SIGNOUT_USER: "SIGNOUT_USER", 9 | 10 | GET_USER_DETAIL: "GET_USER_DETAIL", 11 | GET_USER_DETAIL_SUCCESS: "GET_USER_DETAIL_SUCCESS", 12 | 13 | USER_REQUEST_FAILED: "USER_REQUEST_FAILED", 14 | }; 15 | -------------------------------------------------------------------------------- /app/src/reportWebVitals.ts: -------------------------------------------------------------------------------- 1 | import { ReportHandler } from 'web-vitals'; 2 | 3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => { 4 | if (onPerfEntry && onPerfEntry instanceof Function) { 5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 6 | getCLS(onPerfEntry); 7 | getFID(onPerfEntry); 8 | getFCP(onPerfEntry); 9 | getLCP(onPerfEntry); 10 | getTTFB(onPerfEntry); 11 | }); 12 | } 13 | }; 14 | 15 | export default reportWebVitals; 16 | -------------------------------------------------------------------------------- /app/src/utils/axios/index.tsx: -------------------------------------------------------------------------------- 1 | import axios, { AxiosInstance } from "axios"; 2 | import Cookies from "js-cookie"; 3 | 4 | const Axios: AxiosInstance = axios.create({ 5 | baseURL: process.env.REACT_APP_API_HOST, 6 | }); 7 | 8 | // Add a request interceptor 9 | Axios.interceptors.request.use(function (config: any) { 10 | const token = `Bearer ${Cookies.get("token")}`; 11 | console.log(config, "con"); 12 | config.headers.Authorization = token; 13 | 14 | return config; 15 | }); 16 | 17 | export default Axios; 18 | -------------------------------------------------------------------------------- /app/src/redux/store.tsx: -------------------------------------------------------------------------------- 1 | import { applyMiddleware, createStore } from "redux"; 2 | import createSagaMiddleware from "redux-saga"; 3 | import { RootReducer } from "./reducers"; 4 | import RootSaga from "./sagas/rootSaga"; 5 | 6 | const sagaMiddleware = createSagaMiddleware(); 7 | 8 | const middleware = [sagaMiddleware]; 9 | 10 | const store = createStore(RootReducer, applyMiddleware(...middleware)); 11 | 12 | export type RootState = ReturnType; 13 | export type AppDispatch = typeof store.dispatch; 14 | 15 | sagaMiddleware.run(RootSaga); 16 | export default store; 17 | -------------------------------------------------------------------------------- /server/model/contact.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | const Schema = mongoose.Schema; 3 | 4 | const contactSchema = new mongoose.Schema( 5 | { 6 | user_id: { type: Schema.Types.ObjectId, ref: "user" }, 7 | full_name: { type: String, default: null }, 8 | address: { type: String, default: null }, 9 | email: { type: String }, 10 | phone: { type: Number, default: null }, 11 | favourite: { type: Number, default: 0 }, 12 | image: { type: String }, 13 | }, 14 | { versionKey: false } 15 | ); 16 | 17 | module.exports = mongoose.model("contact", contactSchema); 18 | -------------------------------------------------------------------------------- /app/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 | -------------------------------------------------------------------------------- /server/config/database.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const { MONGO_URI } = process.env; 4 | 5 | exports.connect = () => { 6 | // Connecting to the database 7 | mongoose 8 | .connect(MONGO_URI, { 9 | useNewUrlParser: true, 10 | useUnifiedTopology: true, 11 | // useCreateIndex: true, 12 | // useFindAndModify: false, 13 | }) 14 | .then(() => { 15 | console.log("Successfully connected to database"); 16 | }) 17 | .catch((error) => { 18 | console.log("database connection failed. exiting now..."); 19 | console.error(error); 20 | process.exit(1); 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node index.js", 8 | "dev": "nodemon index.js", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "ISC", 14 | "dependencies": { 15 | "bcryptjs": "^2.4.3", 16 | "body-parser": "^1.19.1", 17 | "cors": "^2.8.5", 18 | "dotenv": "^11.0.0", 19 | "express": "^4.17.2", 20 | "jsonwebtoken": "^8.5.1", 21 | "mongoose": "^6.1.6" 22 | }, 23 | "devDependencies": { 24 | "nodemon": "^2.0.15" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/redux/types/contactsTypes.ts: -------------------------------------------------------------------------------- 1 | export const contacts = { 2 | GETALL_CONTACTS: "GETALL_CONTACTS", 3 | GETALL_CONTACTS_SUCCESS: "GETALL_CONTACTS_SUCCESS", 4 | 5 | CREATE_CONTACT: "CREATE_CONTACT", 6 | CREATE_CONTACT_SUCCESS: "CREATE_CONTACT_SUCCESS", 7 | 8 | UPDATE_CONTACT: "UPDATE_CONTACT", 9 | UPDATE_CONTACT_SUCCESS: "UPDATE_CONTACT_SUCCESS", 10 | 11 | UPDATE_FAVOURITE_CONTACT: "UPDATE_FAVOURITE_CONTACT", 12 | UPDATE_FAVOURITE_CONTACT_SUCCESS: "UPDATE_FAVOURITE_CONTACT_SUCCESS", 13 | 14 | DELETE_CONTACT: "DELETE_CONTACT", 15 | DELETE_CONTACT_SUCCESS: "DELETE_CONTACT_SUCCESS", 16 | 17 | CONTACTS_REQUEST_FAILED: "CONTACTS_REQUEST_FAILED", 18 | }; 19 | -------------------------------------------------------------------------------- /app/src/routes/routes.ts: -------------------------------------------------------------------------------- 1 | import { IRoutesType } from "../interfaces"; 2 | import Contacts from "../pages/Contacts"; 3 | import ContactForm from "../pages/Contacts/ContactForm"; 4 | import NotFound from "../pages/NotFound"; 5 | 6 | export const routers: IRoutesType[] = [ 7 | { 8 | path: "/contact", 9 | title: "Contact", 10 | component: Contacts, 11 | }, 12 | { 13 | path: "/contact/add", 14 | title: "AddContact", 15 | component: ContactForm, 16 | }, 17 | { 18 | path: "/contact/edit", 19 | title: "EditContact", 20 | component: ContactForm, 21 | }, 22 | { 23 | path: "*", 24 | title: "notFouind", 25 | component: NotFound, 26 | }, 27 | ]; 28 | -------------------------------------------------------------------------------- /server/middleware/auth.js: -------------------------------------------------------------------------------- 1 | const jwt = require("jsonwebtoken"); 2 | 3 | const config = process.env; 4 | 5 | const verifyToken = (req, res, next) => { 6 | const bearerHeader = req.headers["authorization"]; 7 | 8 | if (!bearerHeader) { 9 | // Forbidden 10 | res.status(403).send("A token is required for authentication"); 11 | } 12 | 13 | try { 14 | const bearer = bearerHeader.split(" "); 15 | const bearerToken = bearer[1]; 16 | 17 | const decoded = jwt.verify(bearerToken, config.TOKEN_KEY); 18 | req.user = decoded; 19 | } catch (err) { 20 | return res.status(401).send("Invalid Token"); 21 | } 22 | return next(); 23 | }; 24 | 25 | module.exports = verifyToken; 26 | -------------------------------------------------------------------------------- /app/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 | -------------------------------------------------------------------------------- /app/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import ReactDOM from "react-dom"; 4 | import { Provider } from "react-redux"; 5 | import reportWebVitals from "./reportWebVitals"; 6 | import { BrowserRouter } from "react-router-dom"; 7 | 8 | import App from "./App"; 9 | import store from "./redux/store"; 10 | 11 | ReactDOM.render( 12 | // 13 | 14 | 15 | 16 | 17 | , 18 | // , 19 | document.getElementById("root") 20 | ); 21 | 22 | // If you want to start measuring performance in your app, pass a function 23 | // to log results (for example: reportWebVitals(console.log)) 24 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 25 | reportWebVitals(); 26 | -------------------------------------------------------------------------------- /app/src/pages/NotFound/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "@emotion/styled"; 3 | import { Link } from "react-router-dom"; 4 | 5 | import { Color } from "../../constants/Color"; 6 | 7 | const Wrapper = styled("div")` 8 | width: 100%; 9 | height: 100%; 10 | `; 11 | 12 | const Title = styled("h2")` 13 | font-size: 50px; 14 | font-weight: 700; 15 | color: ${Color.textDark}; 16 | text-align: center; 17 | `; 18 | 19 | const Container = styled.div` 20 | height: 60vh; 21 | `; 22 | 23 | const UnderConstruction = () => { 24 | return ( 25 | 26 | 27 | 28 | Page Not Found 29 | 🏠 30 | 31 | 32 | 33 | ); 34 | }; 35 | export default UnderConstruction; 36 | -------------------------------------------------------------------------------- /app/src/components/Custom/CInput/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Input } from "antd"; 3 | import { InputProps } from "antd/lib/input"; 4 | import styled from "@emotion/styled"; 5 | import { Color } from "../../../constants/Color"; 6 | 7 | const StyledInput = styled(Input)` 8 | border: 1px solid #e9e9e9; 9 | box-shadow: 0px 4px 8px #2c27380a; 10 | padding: 16px; 11 | height: 37px; 12 | border-radius: 6px; 13 | 14 | :hover { 15 | background: #ffffff 0% 0% no-repeat padding-box; 16 | box-shadow: 0px 4px 8px #1890ff33; 17 | border: 1px solid ${Color.primary}; 18 | } 19 | ::placeholder { 20 | color: ${Color.textGrey}; 21 | font-size: 13px; 22 | } 23 | :focus { 24 | box-shadow: none !important; 25 | } 26 | `; 27 | export const CInput = React.forwardRef((props: InputProps, ref: any) => { 28 | return ; 29 | }); 30 | -------------------------------------------------------------------------------- /app/src/components/Custom/CTitle/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Typography } from "antd"; 3 | import styled from "@emotion/styled"; 4 | import { Color } from "../../../constants/Color"; 5 | import { TitleProps } from "antd/lib/typography/Title"; 6 | 7 | const { Title } = Typography; 8 | 9 | type Props = TitleProps & { 10 | children: any; 11 | color?: string; 12 | fontWeight?: number; 13 | textalign?: string; 14 | margin?: string; 15 | }; 16 | 17 | const StyledTitle = styled(Title)` 18 | &.ant-typography { 19 | color: ${(props) => props.color || Color.textDark}; 20 | font-weight: ${(props) => props.fontWeight}; 21 | text-align: ${(props) => props.textalign}; 22 | margin: ${(props) => props.margin}; 23 | } 24 | `; 25 | export const CTitle = (props: Props) => { 26 | const { children, ...rest } = props; 27 | return {children}; 28 | }; 29 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | name: Contact-manager CI 2 | 3 | on: 4 | push: 5 | branches: ["master"] 6 | pull_request: 7 | branches: ["master"] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | matrix: 15 | node-version: [18.x] 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | 20 | # Setup Node.js 21 | - name: Use Node.js ${{ matrix.node-version }} 22 | uses: actions/setup-node@v4 23 | with: 24 | node-version: ${{ matrix.node-version }} 25 | 26 | # Install dependencies and build the frontend (app) 27 | - name: Install dependencies (Frontend) 28 | run: yarn install --frozen-lockfile 29 | working-directory: app 30 | 31 | - name: Build frontend 32 | run: yarn build 33 | working-directory: app 34 | 35 | # Install dependencies and build the backend (server) 36 | - name: Install dependencies (Backend) 37 | run: yarn install --frozen-lockfile 38 | working-directory: server 39 | -------------------------------------------------------------------------------- /app/src/components/Custom/CInputPassword/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Input } from "antd"; 3 | import { InputProps } from "antd/lib/input"; 4 | import styled from "@emotion/styled"; 5 | import { Color } from "../../../constants/Color"; 6 | 7 | const StyledInput = styled(Input.Password)` 8 | border: 1px solid #e9e9e9; 9 | box-shadow: 0px 4px 8px #2c27380a; 10 | padding: 12px 16px; 11 | height: 37px; 12 | border-radius: 6px; 13 | :hover { 14 | background: #ffffff 0% 0% no-repeat padding-box; 15 | box-shadow: 0px 4px 8px #1890ff33; 16 | border: 1px solid ${Color.primary}; 17 | } 18 | .ant-input { 19 | :focus { 20 | box-shadow: none !important; 21 | border-color: transparent; 22 | } 23 | ::placeholder { 24 | color: ${Color.textGrey}; 25 | font-size: 13px; 26 | } 27 | :focus { 28 | box-shadow: none !important; 29 | } 30 | } 31 | `; 32 | 33 | export const CInputPassword = React.forwardRef( 34 | (props: InputProps, ref: any) => { 35 | return ; 36 | } 37 | ); 38 | -------------------------------------------------------------------------------- /app/src/components/Custom/CButton/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { Button } from "antd"; 4 | import styled from "@emotion/styled"; 5 | import { ButtonProps } from "antd/lib/button"; 6 | 7 | import { Color } from "../../../constants/Color"; 8 | 9 | type Props = ButtonProps & { 10 | children: any; 11 | backgroundcolor?: string; 12 | color?: string; 13 | }; 14 | 15 | const StyledButton = styled(Button)` 16 | width: 110px; 17 | background: ${(props) => props.backgroundcolor || Color.lightBlue}; 18 | color: ${(props) => props.color || "#fff"}; 19 | font-size: 12px; 20 | font-weight: 600; 21 | height: 35px; 22 | box-shadow: 0px 4px 8px #2c273814; 23 | border-radius: 6px; 24 | &:hover, 25 | &:focus { 26 | background: ${(props) => props.backgroundcolor || Color.lightBlue}; 27 | color: ${(props) => props.color || "#fff"}; 28 | border-color: ${(props) => props.color || "#fff"}; 29 | } 30 | `; 31 | 32 | export const CButton = (props: Props) => { 33 | const { children, ...rest } = props; 34 | return {children}; 35 | }; 36 | -------------------------------------------------------------------------------- /app/src/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | export interface IRoutesType { 2 | path: string; 3 | title: string; 4 | component: React.FC; 5 | } 6 | 7 | export interface ILogin { 8 | email: string; 9 | password: string; 10 | } 11 | 12 | export interface IRegister { 13 | full_name: string; 14 | email: string; 15 | password: string; 16 | } 17 | 18 | export interface ISignupUser extends IRegister { 19 | onSuccess: () => void; 20 | } 21 | 22 | export interface ISigninUser extends ILogin { 23 | onSuccess: () => void; 24 | } 25 | 26 | export interface IContact { 27 | _id: string; 28 | user_id: string; 29 | full_name: string; 30 | address: string; 31 | email: string; 32 | phone: number; 33 | favourite: number; 34 | image: string; 35 | } 36 | 37 | export interface IUser { 38 | _id: string; 39 | full_name: string; 40 | email: string; 41 | } 42 | 43 | export interface IParamsUserId { 44 | user_id: string; 45 | } 46 | export interface IUserID { 47 | params: IParamsUserId; 48 | } 49 | 50 | export interface IDeleteContact { 51 | user_id: string; 52 | _id: string; 53 | onSuccess?: () => void; 54 | } 55 | -------------------------------------------------------------------------------- /app/src/components/Custom/CInputNumber/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import styled from "@emotion/styled"; 4 | import { InputNumber, InputNumberProps } from "antd"; 5 | 6 | import { Color } from "../../../constants/Color"; 7 | 8 | const StyledInput = styled(InputNumber)` 9 | border: 1px solid #e9e9e9; 10 | box-shadow: 0px 4px 8px #2c27380a; 11 | height: 37px; 12 | border-radius: 6px; 13 | width: 100%; 14 | 15 | .ant-input-number-handler-wrap { 16 | display: none; 17 | } 18 | .ant-input-number-input-wrap { 19 | height: 100%; 20 | 21 | input { 22 | height: 100%; 23 | ::placeholder { 24 | color: ${Color.textGrey}; 25 | font-size: 13px; 26 | } 27 | :focus { 28 | box-shadow: none !important; 29 | } 30 | } 31 | } 32 | :hover { 33 | background: #ffffff 0% 0% no-repeat padding-box; 34 | box-shadow: 0px 4px 8px #1890ff33; 35 | border: 1px solid ${Color.primary}; 36 | } 37 | `; 38 | export const CInputNumber = React.forwardRef( 39 | (props: InputNumberProps, ref: any) => { 40 | return ; 41 | } 42 | ); 43 | -------------------------------------------------------------------------------- /app/src/components/Custom/CFormItem/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "@emotion/styled"; 3 | import { Form } from "antd"; 4 | import { FormItemProps } from "antd/lib/form"; 5 | import { Color } from "../../../constants/Color"; 6 | 7 | type Props = FormItemProps & { 8 | children: any; 9 | }; 10 | const FormItem = styled(Form.Item)` 11 | margin-bottom: 15px; 12 | .ant-form-item-label { 13 | line-height: 30px; 14 | padding: 0; 15 | 16 | label { 17 | color: ${Color.textGrey}; 18 | font-weight: 500; 19 | font-size: 12px; 20 | ::after { 21 | display: none; 22 | } 23 | } 24 | } 25 | .ant-form-explain { 26 | font-size: 12px; 27 | transform: translateY(5px); 28 | } 29 | .has-error .ant-select-open .ant-select-selection, 30 | .has-error .ant-select-focused .ant-select-selection { 31 | box-shadow: none !important; 32 | } 33 | .ant-select-selection--multiple > ul > li, 34 | .ant-select-selection--multiple .ant-select-selection__rendered > ul > li { 35 | margin-top: 7px !important; 36 | } 37 | `; 38 | 39 | export const CFormItem = (props: Props) => { 40 | const { children, ...rest } = props; 41 | return {children}; 42 | }; 43 | -------------------------------------------------------------------------------- /app/src/redux/reducers/contactsReducer.ts: -------------------------------------------------------------------------------- 1 | import types from "../types"; 2 | import { AnyAction } from "redux"; 3 | 4 | const initialState = { 5 | isLoading: false, 6 | data: [], 7 | error: null, 8 | }; 9 | 10 | export const contactsReducer = (state = initialState, action: AnyAction) => { 11 | switch (action.type) { 12 | case types.GETALL_CONTACTS: 13 | case types.CREATE_CONTACT: 14 | case types.UPDATE_CONTACT: 15 | case types.DELETE_CONTACT: 16 | case types.UPDATE_FAVOURITE_CONTACT: 17 | return { 18 | ...state, 19 | isLoading: true, 20 | }; 21 | 22 | case types.GETALL_CONTACTS_SUCCESS: 23 | case types.CREATE_CONTACT_SUCCESS: 24 | return { 25 | ...state, 26 | isLoading: false, 27 | data: action.payload, 28 | error: null, 29 | }; 30 | 31 | case types.UPDATE_FAVOURITE_CONTACT_SUCCESS: 32 | case types.DELETE_CONTACT_SUCCESS: 33 | return { 34 | ...state, 35 | loading: false, 36 | error: null, 37 | }; 38 | 39 | case types.CONTACTS_REQUEST_FAILED: 40 | return { 41 | ...state, 42 | error: action.error, 43 | isLoading: false, 44 | }; 45 | 46 | default: 47 | return { ...state }; 48 | } 49 | }; 50 | -------------------------------------------------------------------------------- /app/src/redux/reducers/usersReducer.ts: -------------------------------------------------------------------------------- 1 | import types from "../types"; 2 | import { AnyAction } from "redux"; 3 | 4 | const initialState = { 5 | isLoading: false, 6 | data: [], 7 | error: null, 8 | }; 9 | 10 | export const usersReducer = (state = initialState, action: AnyAction) => { 11 | switch (action.type) { 12 | case types.SIGNIN_USER: 13 | case types.SIGNUP_USER: 14 | case types.GET_USER_DETAIL: 15 | return { 16 | ...state, 17 | isLoading: true, 18 | }; 19 | 20 | case types.SIGNIN_USER_SUCCESS: 21 | case types.SIGNUP_USER_SUCCESS: 22 | return { 23 | ...state, 24 | isLoading: false, 25 | error: null, 26 | data: action.payload, 27 | }; 28 | 29 | case types.GET_USER_DETAIL_SUCCESS: 30 | return { 31 | ...state, 32 | isLoading: false, 33 | data: action.payload, 34 | }; 35 | 36 | case types.USER_REQUEST_FAILED: 37 | return { 38 | ...state, 39 | error: action.error, 40 | isLoading: false, 41 | }; 42 | 43 | case types.SIGNOUT_USER: 44 | return { 45 | ...state, 46 | error: null, 47 | isLoading: false, 48 | }; 49 | 50 | default: 51 | return { ...state }; 52 | } 53 | }; 54 | -------------------------------------------------------------------------------- /app/src/redux/actions/userAction.ts: -------------------------------------------------------------------------------- 1 | import types from "../types"; 2 | import { 3 | IParamsUserId, 4 | ISigninUser, 5 | ISignupUser, 6 | IUser, 7 | IUserID, 8 | } from "../../interfaces"; 9 | 10 | // sign in user 11 | export function signinUser(payload: ISigninUser) { 12 | return { 13 | type: types.SIGNIN_USER, 14 | payload, 15 | }; 16 | } 17 | 18 | export function signinUserSuccess(payload: IParamsUserId) { 19 | return { 20 | type: types.SIGNIN_USER_SUCCESS, 21 | payload, 22 | }; 23 | } 24 | 25 | // sign up user 26 | export function signupUser(payload: ISignupUser) { 27 | return { 28 | type: types.SIGNUP_USER, 29 | payload, 30 | }; 31 | } 32 | 33 | export function signupUserSuccess() { 34 | return { 35 | type: types.SIGNUP_USER_SUCCESS, 36 | }; 37 | } 38 | 39 | // get user detail 40 | export function getUserDetail(payload: IUserID) { 41 | return { 42 | type: types.GET_USER_DETAIL, 43 | payload, 44 | }; 45 | } 46 | 47 | export function getUserDetailSuccess(payload: IUser) { 48 | return { 49 | type: types.GET_USER_DETAIL_SUCCESS, 50 | payload, 51 | }; 52 | } 53 | 54 | // request use failed 55 | export function UserRequestFailed(payload: string) { 56 | return { 57 | type: types.USER_REQUEST_FAILED, 58 | payload, 59 | }; 60 | } 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # contact-manager 2 | 3 | ## App 4 | Open [https://contact-manager-app-244k.onrender.com](https://contact-manager-app-244k.onrender.com) to view it in the browser. 5 | 6 | ## Setup Environment 7 | 8 | Create a `.env` file from `.env.example`.
9 | 10 | ## Available Scripts 11 | 12 | In the project directory, you can run: 13 | 14 | ### `yarn start` 15 | 16 | Runs the app in the development mode.
17 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 18 | 19 | The page will reload if you make edits.
20 | 21 | ### `npm run build` 22 | 23 | Builds the app for production to the `build` folder.
24 | It correctly bundles React in production mode and optimizes the build for the best performance. 25 | 26 | The build is minified and the filenames include the hashes.
27 | Your app is ready to be deployed! 28 | 29 | ## Server 30 | 31 | ## Setup Environment 32 | 33 | Create a `.env` file from `.env.example`.
34 | 35 | ## Available Scripts 36 | 37 | In the project directory, you can run: 38 | 39 | ### `yarn run start` 40 | 41 | Runs the app in the development mode.
42 | Open [http://localhost:4001](http://localhost:4001) to view it in the browser. 43 | 44 | The page will reload if you make edits.
45 | 46 | ## Database(MongoDb) 47 | 48 | Database is hosted in [http:cloud.mongodb.com ](http:cloud.mongodb.com) 49 | -------------------------------------------------------------------------------- /app/src/routes/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Redirect, Route, Switch, withRouter } from "react-router"; 3 | 4 | import moment from "moment"; 5 | import jwt from "jwt-decode"; 6 | import Cookies from "js-cookie"; 7 | 8 | import { Login } from "../pages/Login"; 9 | import { Register } from "../pages/Register"; 10 | import { AppLayout } from "../components/layout"; 11 | 12 | function Routes() { 13 | return ( 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | ); 23 | } 24 | 25 | function auth() { 26 | const token = Cookies.get("token"); 27 | if (token) { 28 | const userDetail: any = jwt(token); 29 | const isExpired = moment.unix(userDetail.exp).diff(moment(), "seconds"); 30 | if (isExpired > 0) { 31 | return true; 32 | } 33 | } 34 | return false; 35 | } 36 | 37 | function PrivateRoute({ children, ...rest }: any) { 38 | return ( 39 | 42 | auth() ? ( 43 | children 44 | ) : ( 45 | 51 | ) 52 | } 53 | /> 54 | ); 55 | } 56 | 57 | export default withRouter(Routes); 58 | -------------------------------------------------------------------------------- /app/src/components/layout/AppLayout.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Layout } from "antd"; 3 | import styled from "@emotion/styled"; 4 | import { Route, Switch } from "react-router"; 5 | 6 | import Profile from "./Profile"; 7 | import { routers } from "../../routes/routes"; 8 | import { IRoutesType } from "../../interfaces"; 9 | import contactmanager from "../../asset/cm.png"; 10 | 11 | const { Header, Content } = Layout; 12 | 13 | const Logo = styled.img``; 14 | 15 | const PrimaryHeader = styled(Header)` 16 | background: #ffffff 0% 0% no-repeat padding-box; 17 | display: flex; 18 | justify-content: space-between; 19 | height: 60px; 20 | line-height: 60px; 21 | padding: 0 20px; 22 | `; 23 | const StyledLayout = styled(Layout)` 24 | background: #f9fcff 0% 0% no-repeat padding-box; 25 | height: 100vh; 26 | `; 27 | 28 | export const TableWrapper = styled.div` 29 | padding: 16px 200px; 30 | `; 31 | 32 | const AppLayout: React.FC = () => { 33 | return ( 34 | 35 | 36 |
37 | 38 |
39 | 40 |
41 | 42 | 43 | 44 | {routers.map((item: IRoutesType) => ( 45 | 51 | ))} 52 | 53 | 54 | 55 |
56 | ); 57 | }; 58 | 59 | export default AppLayout; 60 | -------------------------------------------------------------------------------- /app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@ant-design/icons": "^4.7.0", 7 | "@azure/storage-blob": "^12.8.0", 8 | "@emotion/react": "^11.7.1", 9 | "@emotion/styled": "^11.6.0", 10 | "@redux-saga/types": "^1.1.0", 11 | "@testing-library/jest-dom": "^5.14.1", 12 | "@testing-library/react": "^12.0.0", 13 | "@testing-library/user-event": "^13.2.1", 14 | "@types/jest": "^27.0.1", 15 | "@types/js-cookie": "^3.0.1", 16 | "@types/node": "^16.7.13", 17 | "@types/react": "^17.0.20", 18 | "@types/react-dom": "^17.0.9", 19 | "@types/react-redux": "^7.1.22", 20 | "@types/react-router": "5.1.16", 21 | "@types/react-router-dom": "5.1.8", 22 | "antd": "^4.18.2", 23 | "antd-img-crop": "^4.1.0", 24 | "axios": "^0.24.0", 25 | "js-cookie": "^3.0.1", 26 | "jwt-decode": "^3.1.2", 27 | "moment": "^2.29.1", 28 | "react": "^17.0.2", 29 | "react-dom": "^17.0.2", 30 | "react-redux": "^7.2.6", 31 | "react-router-dom": "5.2.0", 32 | "react-scripts": "5.0.0", 33 | "redux-saga": "^1.1.3", 34 | "typescript": "^4.4.2", 35 | "web-vitals": "^2.1.0" 36 | }, 37 | "scripts": { 38 | "start": "react-scripts start", 39 | "build": "react-scripts build", 40 | "test": "react-scripts test", 41 | "eject": "react-scripts eject" 42 | }, 43 | "eslintConfig": { 44 | "extends": [ 45 | "react-app", 46 | "react-app/jest" 47 | ] 48 | }, 49 | "browserslist": { 50 | "production": [ 51 | ">0.2%", 52 | "not dead", 53 | "not op_mini all" 54 | ], 55 | "development": [ 56 | "last 1 chrome version", 57 | "last 1 firefox version", 58 | "last 1 safari version" 59 | ] 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app/src/redux/actions/contactsAction.ts: -------------------------------------------------------------------------------- 1 | import types from "../types"; 2 | import { IContact, IDeleteContact, IUserID } from "../../interfaces"; 3 | 4 | export function getAllContacts(payload: IUserID) { 5 | return { 6 | type: types.GETALL_CONTACTS, 7 | payload, 8 | }; 9 | } 10 | 11 | export function getAllContactsSuccess(payload: IContact) { 12 | return { 13 | type: types.GETALL_CONTACTS_SUCCESS, 14 | payload, 15 | }; 16 | } 17 | 18 | export function createContacts(payload: IContact) { 19 | return { 20 | type: types.CREATE_CONTACT, 21 | payload, 22 | }; 23 | } 24 | 25 | export function createContactsSuccess(payload: any) { 26 | return { 27 | type: types.DELETE_CONTACT_SUCCESS, 28 | payload, 29 | }; 30 | } 31 | 32 | export function deleteContact(payload: any) { 33 | return { 34 | type: types.DELETE_CONTACT, 35 | payload, 36 | }; 37 | } 38 | 39 | export function deleteContactSuccess(payload: IDeleteContact) { 40 | return { 41 | type: types.DELETE_CONTACT_SUCCESS, 42 | payload, 43 | }; 44 | } 45 | 46 | export function updateFavouriteContact(payload: any) { 47 | return { 48 | type: types.UPDATE_FAVOURITE_CONTACT, 49 | payload, 50 | }; 51 | } 52 | 53 | export function updateFavouriteContactSuccess(payload: IDeleteContact) { 54 | return { 55 | type: types.UPDATE_FAVOURITE_CONTACT_SUCCESS, 56 | payload, 57 | }; 58 | } 59 | 60 | export function updateContact(payload: IContact) { 61 | return { 62 | type: types.UPDATE_CONTACT, 63 | payload, 64 | }; 65 | } 66 | 67 | export function updateContactSuccess(payload: IContact) { 68 | return { 69 | type: types.UPDATE_CONTACT_SUCCESS, 70 | payload, 71 | }; 72 | } 73 | 74 | export function contactsRequestFailed(payload: string) { 75 | return { 76 | type: types.CONTACTS_REQUEST_FAILED, 77 | payload, 78 | }; 79 | } 80 | -------------------------------------------------------------------------------- /app/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | Contact Manager 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /app/src/components/TopHeader/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import styled from "@emotion/styled"; 4 | import { Col, Row, Space } from "antd"; 5 | 6 | import { Color } from "../../constants/Color"; 7 | import { CButton, CTitle } from "../../components/Custom"; 8 | 9 | type Props = { 10 | title: string; 11 | save?: string; 12 | cancel?: string; 13 | onSave?: () => void; 14 | }; 15 | 16 | const Wrapper = styled.section` 17 | padding: 16px; 18 | line-height: 28px; 19 | `; 20 | 21 | const CancelButton = styled(CButton)` 22 | height: 30px; 23 | border: 1px solid #2e2e2e; 24 | border-radius: 6px; 25 | background: #f9fcff; 26 | :hover { 27 | box-shadow: inset 0px 0px 1px 0 #2e2e2e; 28 | } 29 | `; 30 | 31 | const SaveButton = styled(CButton)` 32 | height: 30px; 33 | `; 34 | 35 | const PageTitle = styled(CTitle)` 36 | letter-spacing: 0px; 37 | font-size: 18px; 38 | `; 39 | 40 | const TopHeader: React.FC = ({ title, save, cancel, onSave }) => { 41 | return ( 42 | 43 | 44 | 45 | 46 | {title} 47 | 48 | 49 | 50 | 51 | {cancel && ( 52 | window.history.back()} 57 | > 58 | {cancel} 59 | 60 | )} 61 | {save && ( 62 | 63 | {save} 64 | 65 | )} 66 | 67 | 68 | 69 | 70 | ); 71 | }; 72 | 73 | export default TopHeader; 74 | -------------------------------------------------------------------------------- /app/src/components/Custom/CTable/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Table } from "antd"; 3 | import styled from "@emotion/styled"; 4 | import { Color } from "../../../constants/Color"; 5 | 6 | const StyledTable = styled(Table)` 7 | width: 100%; 8 | .ant-table { 9 | background: #ffffff 0% 0% no-repeat padding-box; 10 | box-shadow: 0px 25px 40px #0000000d; 11 | border-radius: 10px; 12 | } 13 | table { 14 | thead { 15 | tr { 16 | background: #fdfdfd; 17 | th { 18 | background: #ffffff; 19 | letter-spacing: 0.14px; 20 | color: ${Color.textDark}; 21 | font-size: 13px; 22 | font-weight: 500; 23 | &:before { 24 | display: none; 25 | } 26 | } 27 | } 28 | } 29 | tbody { 30 | .ant-table-row { 31 | color: ${Color.textDark}; 32 | font-weight: 500; 33 | &:last-child { 34 | td { 35 | &:first-of-type { 36 | border-top-left-radius: 10px !important; 37 | border-bottom-left-radius: 10px !important; 38 | } 39 | &:last-child { 40 | border-top-right-radius: 10px !important; 41 | border-bottom-right-radius: 10px !important; 42 | } 43 | } 44 | } 45 | td { 46 | background: #fff; 47 | padding: 8px 16px; 48 | font-size: 12px; 49 | :hover { 50 | background: #2e4dd40d !important; 51 | } 52 | } 53 | :hover { 54 | cursor: pointer; 55 | } 56 | } 57 | } 58 | } 59 | .ant-table-empty { 60 | .ant-table-placeholder { 61 | .ant-table-cell { 62 | border-bottom: none; 63 | } 64 | :hover { 65 | .ant-table-cell { 66 | border-bottom: none; 67 | } 68 | } 69 | } 70 | } 71 | `; 72 | 73 | export const CTable = React.forwardRef((props: any, ref: any) => { 74 | return ; 75 | }); 76 | -------------------------------------------------------------------------------- /app/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 | ### `yarn start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.\ 15 | You will also see any lint errors in the console. 16 | 17 | ### `yarn 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 | ### `yarn 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 | ### `yarn 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 | -------------------------------------------------------------------------------- /app/src/redux/sagas/usersSaga.ts: -------------------------------------------------------------------------------- 1 | import Cookies from "js-cookie"; 2 | import { notification } from "antd"; 3 | import { AxiosResponse } from "axios"; 4 | import { call, put, takeEvery } from "redux-saga/effects"; 5 | 6 | import types from "../types"; 7 | import { AnyAction } from "redux"; 8 | import Axios from "../../utils/axios"; 9 | import { 10 | getUserDetailSuccess, 11 | signinUserSuccess, 12 | signupUserSuccess, 13 | UserRequestFailed, 14 | } from "../actions/userAction"; 15 | 16 | function* signinUser(action: AnyAction) { 17 | try { 18 | const response: AxiosResponse = yield call(() => 19 | Axios.post("signin/", action.payload) 20 | ); 21 | Cookies.set("token", response.data.token); 22 | Cookies.set("userId", response.data._id); 23 | yield put(signinUserSuccess(response.data)); 24 | yield call(action.payload.onSuccess); 25 | } catch (error: any) { 26 | yield put(UserRequestFailed(error)); 27 | notification.error({ 28 | message: "Sign in Failed", 29 | description: error.response.data.detail, 30 | duration: 2, 31 | }); 32 | } 33 | } 34 | 35 | function* signupUser(action: AnyAction) { 36 | try { 37 | yield call(() => 38 | Axios.post("signup/", action.payload) 39 | ); 40 | yield put(signupUserSuccess()); 41 | yield call(action.payload.onSuccess); 42 | } catch (error: any) { 43 | yield put(UserRequestFailed(error)); 44 | notification.error({ 45 | message: "Sign up Failed", 46 | description: error.response.data.detail, 47 | duration: 2, 48 | }); 49 | } 50 | } 51 | 52 | function* getUserDetail(action: AnyAction) { 53 | try { 54 | const response: AxiosResponse = yield call(() => 55 | Axios.get("user-detail/", action.payload) 56 | ); 57 | yield put(getUserDetailSuccess(response && response.data)); 58 | } catch (error: any) { 59 | yield put(UserRequestFailed(error)); 60 | notification.error({ 61 | message: "loading user detail failed", 62 | description: error.response.data.detail, 63 | duration: 2, 64 | }); 65 | } 66 | } 67 | 68 | export function* usersSaga() { 69 | yield takeEvery(types.SIGNIN_USER, signinUser); 70 | yield takeEvery(types.SIGNUP_USER, signupUser); 71 | yield takeEvery(types.GET_USER_DETAIL, getUserDetail); 72 | } 73 | -------------------------------------------------------------------------------- /app/src/pages/Contacts/UploadProfile.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | 3 | import ImgCrop from "antd-img-crop"; 4 | import styled from "@emotion/styled"; 5 | import { message, Upload } from "antd"; 6 | import { PlusOutlined, LoadingOutlined } from "@ant-design/icons"; 7 | 8 | const UploadPic = styled(Upload)` 9 | .ant-upload { 10 | position: absolute; 11 | top: 50%; 12 | left: 50%; 13 | transform: translate(-50%, -50%); 14 | width: 200px; 15 | height: 200px; 16 | border: 1px solid #ffffff; 17 | box-shadow: inset 0px 0px 19px #2c2e320d; 18 | background: white; 19 | } 20 | `; 21 | 22 | const ProfilePic = styled.img` 23 | width: 100%; 24 | height: 100%; 25 | `; 26 | 27 | type uploadProfileProps = { 28 | imageurl: string; 29 | setImageurl: React.Dispatch>; 30 | }; 31 | 32 | const UploadProfile = (props: uploadProfileProps) => { 33 | const { imageurl, setImageurl } = props; 34 | 35 | const [loading, setLoading] = useState(false); 36 | 37 | function beforeUpload(file: any) { 38 | const isJpgOrPng = file.type === "image/jpeg" || file.type === "image/png"; 39 | if (!isJpgOrPng) { 40 | message.error("You can only upload JPG/PNG file!"); 41 | } 42 | const isLt2M = file.size / 1024 / 1024 < 10; 43 | if (!isLt2M) { 44 | message.error("Image must smaller than 10MB!"); 45 | } 46 | return isJpgOrPng && isLt2M; 47 | } 48 | 49 | const handleChange = (info: any) => { 50 | if (info.file.status === "uploading") { 51 | setLoading(true); 52 | return; 53 | } 54 | if (info.file.status === "done") { 55 | const displayImg = info.file.response.data.image.url; 56 | setImageurl(displayImg); 57 | setLoading(false); 58 | } 59 | }; 60 | 61 | const uploadButton = ( 62 |
63 | {loading ? : } 64 |
Upload
65 |
66 | ); 67 | 68 | return ( 69 | 70 | 79 | {imageurl ? : uploadButton} 80 | 81 | 82 | ); 83 | }; 84 | 85 | export default UploadProfile; 86 | -------------------------------------------------------------------------------- /app/src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/src/pages/Login/Login.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import styled from "@emotion/styled"; 4 | import { Link } from "react-router-dom"; 5 | import { useForm } from "antd/lib/form/Form"; 6 | import { Form, notification } from "antd"; 7 | 8 | import { useHistory } from "react-router"; 9 | import { ILogin } from "../../interfaces"; 10 | import { signinUser } from "../../redux/actions/userAction"; 11 | import { 12 | CInput, 13 | CFormItem, 14 | CInputPassword, 15 | CButton, 16 | } from "../../components/Custom"; 17 | import { useAppDispatch, useAppSelector } from "../../utils/reduxHooks"; 18 | 19 | const Wrapper = styled.div` 20 | position: absolute; 21 | top: 50%; 22 | left: 50%; 23 | transform: translate(-50%, -50%); 24 | width: 20%; 25 | box-shadow: 0 2px 4px rgb(0 0 0 / 10%), 0 8px 16px rgb(0 0 0 / 10%); 26 | padding: 20px; 27 | border-radius: 8px; 28 | .ant-form-item-control-input-content { 29 | text-align: center; 30 | } 31 | `; 32 | 33 | const Register = styled.div` 34 | padding-top: 10px; 35 | `; 36 | 37 | const Login = () => { 38 | const [form] = useForm(); 39 | const dispatch = useAppDispatch(); 40 | const history = useHistory(); 41 | const { isLoading } = useAppSelector((state) => state.users); 42 | 43 | const onFinish = (values: ILogin) => { 44 | dispatch( 45 | signinUser({ 46 | ...values, 47 | onSuccess: () => { 48 | notification.success({ 49 | message: "Success", 50 | description: "Login successfully", 51 | duration: 2, 52 | }); 53 | history.push("/contact"); 54 | }, 55 | }) 56 | ); 57 | }; 58 | 59 | return ( 60 | 61 |
62 | 66 | 67 | 68 | 69 | 73 | 74 | 75 | 76 | 77 | 78 | Sign in 79 | 80 | 81 | Or Sign Up! 82 | 83 | 84 |
85 |
86 | ); 87 | }; 88 | 89 | export default Login; 90 | -------------------------------------------------------------------------------- /app/src/components/layout/Profile.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | 3 | import Cookies from "js-cookie"; 4 | import styled from "@emotion/styled"; 5 | import { useHistory } from "react-router"; 6 | import { LogoutOutlined } from "@ant-design/icons"; 7 | import { Avatar, Button, Col, Dropdown, Menu, Row } from "antd"; 8 | 9 | import { Color } from "../../constants/Color"; 10 | 11 | import { getFirstLetter } from "../../utils/getFirstLetter"; 12 | import { getUserDetail } from "../../redux/actions/userAction"; 13 | import { useAppDispatch, useAppSelector } from "../../utils/reduxHooks"; 14 | 15 | const FullName = styled("p")` 16 | font-size: 14px; 17 | font-weight: 600; 18 | letter-spacing: 0px; 19 | color: ${Color.textDark}; 20 | margin-bottom: 0; 21 | line-height: 25px; 22 | text-transform: capitalize; 23 | `; 24 | 25 | const Designation = styled("p")` 26 | font-size: 13px; 27 | letter-spacing: 0px; 28 | color: ${Color.textGrey}; 29 | margin-bottom: 0; 30 | line-height: 12px; 31 | `; 32 | 33 | const ProfileSection = () => { 34 | const history = useHistory(); 35 | const dispatch = useAppDispatch(); 36 | const { data } = useAppSelector((state) => state.users); 37 | const userId: any = Cookies.get("userId"); 38 | 39 | useEffect(() => { 40 | dispatch(getUserDetail({ params: { user_id: userId } })); 41 | }, [dispatch, userId]); 42 | 43 | const logOut = () => { 44 | Cookies.remove("token"); 45 | Cookies.remove("userId"); 46 | 47 | dispatch({ 48 | type: "SIGNOUT_USER", 49 | }); 50 | history.push("/"); 51 | }; 52 | const menu = ( 53 | 54 | 55 | 59 | 60 | 61 | ); 62 | return ( 63 | 64 | 65 | {data?.full_name} 66 | {data?.email} 67 | 68 | 69 | 75 | 82 | {getFirstLetter(data?.full_name)} 83 | 84 | 85 | 86 | 87 | ); 88 | }; 89 | 90 | export default ProfileSection; 91 | -------------------------------------------------------------------------------- /app/src/pages/Register/Register.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import styled from "@emotion/styled"; 4 | import { Link } from "react-router-dom"; 5 | import { Form, notification } from "antd"; 6 | import { useHistory } from "react-router"; 7 | import { useForm } from "antd/lib/form/Form"; 8 | 9 | import { IRegister } from "../../interfaces"; 10 | import { signupUser } from "../../redux/actions/userAction"; 11 | import { useAppDispatch, useAppSelector } from "../../utils/reduxHooks"; 12 | import { 13 | CInput, 14 | CFormItem, 15 | CInputPassword, 16 | CButton, 17 | } from "../../components/Custom"; 18 | 19 | const Wrapper = styled.div` 20 | position: absolute; 21 | top: 50%; 22 | left: 50%; 23 | transform: translate(-50%, -50%); 24 | width: 20%; 25 | box-shadow: 0 2px 4px rgb(0 0 0 / 10%), 0 8px 16px rgb(0 0 0 / 10%); 26 | padding: 20px; 27 | border-radius: 8px; 28 | .ant-form-item-control-input-content { 29 | text-align: center; 30 | } 31 | `; 32 | 33 | const Login = styled.div` 34 | padding-top: 10px; 35 | `; 36 | const Register = () => { 37 | const [form] = useForm(); 38 | const dispatch = useAppDispatch(); 39 | const history = useHistory(); 40 | 41 | const { isLoading } = useAppSelector((state) => state.users); 42 | 43 | const onFinish = (values: IRegister) => { 44 | dispatch( 45 | signupUser({ 46 | ...values, 47 | onSuccess: () => { 48 | notification.success({ 49 | message: "Success", 50 | description: "Register successfully", 51 | duration: 2, 52 | }); 53 | history.push("/"); 54 | }, 55 | }) 56 | ); 57 | }; 58 | 59 | return ( 60 | 61 |
62 | 66 | 67 | 68 | 69 | 79 | 80 | 81 | 82 | 92 | 93 | 94 | 95 | 96 | 97 | Sign Up 98 | 99 | 100 | Or Sign in! 101 | 102 | 103 |
104 |
105 | ); 106 | }; 107 | 108 | export default Register; 109 | -------------------------------------------------------------------------------- /app/src/redux/sagas/contactsSaga.ts: -------------------------------------------------------------------------------- 1 | import { notification } from "antd"; 2 | import { AxiosResponse } from "axios"; 3 | import { call, put, takeEvery } from "redux-saga/effects"; 4 | 5 | import types from "../types"; 6 | import { AnyAction } from "redux"; 7 | import Axios from "../../utils/axios"; 8 | import { 9 | contactsRequestFailed, 10 | createContactsSuccess, 11 | deleteContactSuccess, 12 | getAllContactsSuccess, 13 | updateContactSuccess, 14 | updateFavouriteContactSuccess, 15 | } from "../actions/contactsAction"; 16 | 17 | function* getAllContacts(action: AnyAction) { 18 | try { 19 | const response: AxiosResponse = yield call(() => 20 | Axios.get("fetch-contacts/", action.payload) 21 | ); 22 | yield put(getAllContactsSuccess(response.data)); 23 | } catch (error: any) { 24 | yield put(contactsRequestFailed(error)); 25 | notification.error({ 26 | message: "Loading contacts failed.", 27 | description: 28 | error.response && error.response.data && error.response.data.detail, 29 | duration: 2, 30 | }); 31 | } 32 | } 33 | 34 | function* createContacts(action: AnyAction) { 35 | try { 36 | const response: AxiosResponse = yield call(() => 37 | Axios.post("create-contact/", action.payload) 38 | ); 39 | yield put(createContactsSuccess(response && response.data)); 40 | yield call(action.payload.onSuccess); 41 | } catch (error: any) { 42 | console.log(error); 43 | yield put(contactsRequestFailed(error)); 44 | notification.error({ 45 | message: "creating contacts failed.", 46 | description: 47 | error.response && error.response.data && error.response.data.detail, 48 | duration: 2, 49 | }); 50 | } 51 | } 52 | 53 | function* updateContact(action: AnyAction) { 54 | try { 55 | const response: AxiosResponse = yield call(() => 56 | Axios.put("update-contact/", action.payload) 57 | ); 58 | yield put(updateContactSuccess(response.data)); 59 | yield call(action.payload.onSuccess); 60 | } catch (error: any) { 61 | console.log(error); 62 | yield put(contactsRequestFailed(error)); 63 | notification.error({ 64 | message: "updating contacts failed.", 65 | description: 66 | error.response && error.response.data && error.response.data.detail, 67 | duration: 2, 68 | }); 69 | } 70 | } 71 | 72 | function* deleteContact(action: AnyAction) { 73 | try { 74 | const response: AxiosResponse = yield call(() => 75 | Axios.delete("remove-contact/", action.payload) 76 | ); 77 | yield put(deleteContactSuccess(response.data)); 78 | yield call(action.payload.onSuccess); 79 | } catch (error: any) { 80 | console.log(error); 81 | yield put(contactsRequestFailed(error)); 82 | notification.error({ 83 | message: "Deleting contacts failed.", 84 | description: 85 | error.response && error.response.data && error.response.data.detail, 86 | duration: 2, 87 | }); 88 | } 89 | } 90 | 91 | function* updateFavouriteContact(action: AnyAction) { 92 | try { 93 | const response: AxiosResponse = yield call(() => 94 | Axios.put("favourite/", action.payload) 95 | ); 96 | yield put(updateFavouriteContactSuccess(response.data)); 97 | yield call(action.payload.onSuccess); 98 | } catch (error: any) { 99 | yield put(contactsRequestFailed(error)); 100 | notification.error({ 101 | message: "Updating Favourite contacts failed.", 102 | description: 103 | error.response && error.response.data && error.response.data.detail, 104 | duration: 2, 105 | }); 106 | } 107 | } 108 | 109 | export function* contactsSaga() { 110 | yield takeEvery(types.GETALL_CONTACTS, getAllContacts); 111 | yield takeEvery(types.CREATE_CONTACT, createContacts); 112 | yield takeEvery(types.DELETE_CONTACT, deleteContact); 113 | yield takeEvery(types.UPDATE_CONTACT, updateContact); 114 | yield takeEvery(types.UPDATE_FAVOURITE_CONTACT, updateFavouriteContact); 115 | } 116 | -------------------------------------------------------------------------------- /app/src/pages/Contacts/ContactForm.tsx: -------------------------------------------------------------------------------- 1 | import Cookies from "js-cookie"; 2 | import { useHistory } from "react-router"; 3 | import React, { useEffect, useState } from "react"; 4 | 5 | import styled from "@emotion/styled"; 6 | import { Col, Form, notification, Row } from "antd"; 7 | 8 | import { 9 | createContacts, 10 | updateContact, 11 | } from "../../redux/actions/contactsAction"; 12 | import UploadProfile from "./UploadProfile"; 13 | import TopHeader from "../../components/TopHeader"; 14 | import { useAppDispatch } from "../../utils/reduxHooks"; 15 | import { CFormItem, CInput, CInputNumber } from "../../components/Custom"; 16 | 17 | const StyledForm = styled(Form)` 18 | .ant-form-item-label { 19 | padding: 0; 20 | } 21 | `; 22 | 23 | const FormWrapper = styled(Row)` 24 | background: #ffffff 0% 0% no-repeat padding-box; 25 | border-radius: 15px; 26 | box-shadow: 0px 4px 8px #0062ff0d; 27 | margin: 0 16px; 28 | padding: 20px; 29 | `; 30 | 31 | const UploadCol = styled(Col)` 32 | position: relative; 33 | `; 34 | 35 | const ContactForm = (props: any) => { 36 | const [imageurl, setImageurl] = useState(""); 37 | const dispatch = useAppDispatch(); 38 | const history = useHistory(); 39 | const userId = Cookies.get("userId"); 40 | const [form] = Form.useForm(); 41 | 42 | const contactDetail = props.location.state; 43 | 44 | useEffect(() => { 45 | form.setFieldsValue({ ...contactDetail }); 46 | }, [contactDetail, form]); 47 | 48 | const saveForm = (values: any) => { 49 | const data = { ...values, user_id: userId, image: imageurl }; 50 | 51 | dispatch( 52 | createContacts({ 53 | ...data, 54 | onSuccess: () => { 55 | notification.success({ 56 | message: "Success", 57 | description: "Contact created successfully", 58 | duration: 2, 59 | }); 60 | history.goBack(); 61 | }, 62 | }) 63 | ); 64 | }; 65 | 66 | const EditForm = (values: any) => { 67 | const data = { 68 | ...values, 69 | user_id: userId, 70 | _id: contactDetail._id, 71 | image: imageurl, 72 | }; 73 | 74 | dispatch( 75 | updateContact({ 76 | ...data, 77 | onSuccess: () => { 78 | notification.success({ 79 | message: "Success", 80 | description: "Edited successfully", 81 | duration: 2, 82 | }); 83 | history.push("/contact"); 84 | }, 85 | }) 86 | ); 87 | }; 88 | return ( 89 | 95 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 117 | 118 | 119 | 120 | 121 | 122 | 137 | 138 | 139 | 140 | 141 | 142 | 153 | 154 | 155 | 156 | 157 | 158 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | ); 175 | }; 176 | 177 | export default ContactForm; 178 | -------------------------------------------------------------------------------- /app/src/pages/Contacts/index.tsx: -------------------------------------------------------------------------------- 1 | import Cookies from "js-cookie"; 2 | import styled from "@emotion/styled"; 3 | import { useHistory } from "react-router-dom"; 4 | import React, { useEffect, useMemo } from "react"; 5 | 6 | import { 7 | Tooltip, 8 | Card, 9 | List, 10 | Avatar, 11 | Row, 12 | Col, 13 | Popconfirm, 14 | notification, 15 | Rate, 16 | Empty, 17 | } from "antd"; 18 | import { EditOutlined, DeleteOutlined } from "@ant-design/icons"; 19 | 20 | import { IContact } from "../../interfaces"; 21 | import { CTable } from "../../components/Custom"; 22 | import TopHeader from "../../components/TopHeader"; 23 | import { 24 | deleteContact, 25 | getAllContacts, 26 | updateFavouriteContact, 27 | } from "../../redux/actions/contactsAction"; 28 | import { useAppDispatch, useAppSelector } from "../../utils/reduxHooks"; 29 | 30 | export const TableWrapper = styled.div` 31 | padding: 16px 0px; 32 | `; 33 | 34 | const StyledCard = styled(Card)` 35 | border-radius: 20px; 36 | margin: 10px; 37 | `; 38 | 39 | const Icons = styled(Row)` 40 | justify-content: space-evenly; 41 | `; 42 | 43 | const Edit = styled(EditOutlined)` 44 | font-size: 18px; 45 | `; 46 | 47 | const Delete = styled(DeleteOutlined)` 48 | font-size: 18px; 49 | `; 50 | 51 | const ListItem = styled(List.Item)` 52 | .ant-list-item-meta-avatar { 53 | align-self: center; 54 | .ant-avatar { 55 | color: rgb(245, 106, 0); 56 | background-color: rgb(253, 227, 207); 57 | } 58 | } 59 | `; 60 | 61 | const Contacts = () => { 62 | const history = useHistory(); 63 | const dispatch = useAppDispatch(); 64 | const { isLoading, data } = useAppSelector((state) => state.contacts); 65 | 66 | const userId: any = Cookies.get("userId"); 67 | 68 | const filterData: any = useMemo(() => { 69 | const favouriteData = data 70 | .filter((item: IContact) => item.favourite === 1) 71 | .sort(); 72 | 73 | const normalData = data 74 | .filter((item: IContact) => item.favourite === 0) 75 | .sort(); 76 | 77 | return [...favouriteData, ...normalData]; 78 | }, [data]); 79 | 80 | useEffect(() => { 81 | dispatch(getAllContacts({ params: { user_id: userId } })); 82 | }, [dispatch, userId]); 83 | 84 | const OnDelete = (id: string, userId: string) => { 85 | const rawData = { 86 | _id: id, 87 | user_id: userId, 88 | }; 89 | 90 | dispatch( 91 | deleteContact({ 92 | params: { ...rawData }, 93 | onSuccess: () => { 94 | notification.success({ 95 | message: "Success", 96 | description: "Deleted contact successfully", 97 | duration: 2, 98 | }); 99 | dispatch(getAllContacts({ params: { user_id: userId } })); 100 | }, 101 | }) 102 | ); 103 | }; 104 | 105 | const onClickFavorite = (value: number, id: string, userId: string) => { 106 | const data = { 107 | user_id: userId, 108 | contact_id: id, 109 | favourite: value, 110 | }; 111 | 112 | const favMessage = value ? "added" : "removed"; 113 | dispatch( 114 | updateFavouriteContact({ 115 | ...data, 116 | onSuccess: () => { 117 | notification.success({ 118 | message: "Success", 119 | description: `Favourite contact ${favMessage} successfully`, 120 | duration: 2, 121 | }); 122 | dispatch(getAllContacts({ params: { user_id: userId } })); 123 | }, 124 | }) 125 | ); 126 | }; 127 | 128 | const columns = [ 129 | { 130 | title: "", 131 | key: "fav", 132 | width: 30, 133 | render: (record: IContact) => { 134 | return ( 135 | 139 | onClickFavorite(value, record._id, userId) 140 | } 141 | /> 142 | ); 143 | }, 144 | }, 145 | { 146 | title: "Contacts", 147 | key: "contacts", 148 | width: 300, 149 | render: (record: IContact) => { 150 | return ( 151 | 152 | } 154 | title={record.full_name} 155 | description={record.email} 156 | /> 157 | 158 | ); 159 | }, 160 | }, 161 | 162 | { 163 | title: "Phone Number", 164 | dataIndex: "phone", 165 | key: "phone", 166 | }, 167 | { 168 | title: "Address", 169 | dataIndex: "address", 170 | key: "address", 171 | }, 172 | 173 | { 174 | title: "", 175 | key: "icons", 176 | render: (record: IContact) => { 177 | return ( 178 | 179 | 180 | 181 | history.push("/contact/edit", record)} /> 182 | 183 | 184 | 185 | OnDelete(record._id, userId)} 190 | > 191 | 192 | 193 | 194 | 195 | ); 196 | }, 197 | }, 198 | ]; 199 | 200 | let locale = { 201 | emptyText: ( 202 | 206 | ), 207 | }; 208 | 209 | return ( 210 |
211 | history.push("/contact/add")} 215 | /> 216 | 217 | 218 | 219 | 227 | 228 | 229 |
230 | ); 231 | }; 232 | 233 | export default Contacts; 234 | -------------------------------------------------------------------------------- /server/app.js: -------------------------------------------------------------------------------- 1 | require("dotenv").config(); 2 | require("./config/database").connect(); 3 | const express = require("express"); 4 | const cors = require("cors"); 5 | 6 | // importing user context 7 | const User = require("./model/user"); 8 | const Contact = require("./model/contact"); 9 | 10 | const bcrypt = require("bcryptjs"); 11 | const jwt = require("jsonwebtoken"); 12 | const auth = require("./middleware/auth"); 13 | 14 | const app = express(); 15 | app.use(cors()); 16 | 17 | app.use(express.json()); 18 | 19 | // Signup 20 | app.post("/signup", async (req, res) => { 21 | // Our signup logic starts here 22 | try { 23 | // Get user input 24 | const { full_name, email, password } = req.body; 25 | 26 | // Validate user input 27 | if (!(email && password && full_name)) { 28 | return res.status(400).send({ detail: "All input is required" }); 29 | } 30 | 31 | // check if user already exist 32 | // Validate if user exist in our database 33 | const oldUser = await User.findOne({ email }); 34 | 35 | if (oldUser) { 36 | return res 37 | .status(409) 38 | .send({ detail: "User Already Exist. Please Sign in" }); 39 | } 40 | 41 | //Encrypt user password 42 | encryptedPassword = await bcrypt.hash(password, 10); 43 | 44 | // Create user in our database 45 | const user = await User.create({ 46 | full_name, 47 | email: email.toLowerCase(), // sanitize: convert email to lowercase 48 | password: encryptedPassword, 49 | }); 50 | 51 | // Create token 52 | const token = jwt.sign( 53 | { user_id: user._id, email }, 54 | process.env.TOKEN_KEY, 55 | { 56 | expiresIn: "2h", 57 | } 58 | ); 59 | 60 | // save user token 61 | user.token = token; 62 | 63 | // return new user 64 | res.status(201).json(user); 65 | } catch (err) { 66 | console.log(err); 67 | } 68 | 69 | // Our signup logic ends here 70 | }); 71 | 72 | // Signin 73 | app.post("/signin", async (req, res) => { 74 | // Our Sign in logic starts here 75 | try { 76 | // Get user input 77 | const { email, password } = req.body; 78 | 79 | // Validate user input 80 | if (!(email && password)) { 81 | res.status(400).send({ detail: "All input is required" }); 82 | } 83 | // Validate if user exist in our database 84 | const user = await User.findOne({ email }); 85 | 86 | if (user && (await bcrypt.compare(password, user.password))) { 87 | // Create token 88 | const token = jwt.sign( 89 | { user_id: user._id, email }, 90 | process.env.TOKEN_KEY, 91 | { 92 | expiresIn: "2h", 93 | } 94 | ); 95 | 96 | // save user token 97 | user.token = token; 98 | 99 | // user 100 | res.status(200).json(user); 101 | } else { 102 | res.status(400).send({ detail: "Invalid Credentials" }); 103 | } 104 | } catch (err) { 105 | console.log(err); 106 | } 107 | // Our sign in logic ends here 108 | }); 109 | 110 | app.post("/create-contact", auth, async (req, res) => { 111 | try { 112 | const { full_name, email, phone, address, user_id, image } = req.body; 113 | 114 | if (!(email && full_name && phone && address)) { 115 | return res.status(400).send({ detail: "All input is required" }); 116 | } 117 | 118 | if (!image) { 119 | return res.status(400).send({ detail: "Please upload image" }); 120 | } 121 | 122 | const oldContact = await Contact.findOne({ phone, user_id }); 123 | 124 | if (oldContact) { 125 | return res 126 | .status(409) 127 | .send({ detail: "Contact number Exist with different name." }); 128 | } 129 | 130 | // Create Contact in our database 131 | const contact = await Contact.create({ 132 | user_id, 133 | full_name, 134 | phone, 135 | email: email.toLowerCase(), // sanitize: convert email to lowercase 136 | address, 137 | image, 138 | }); 139 | 140 | res.status(201).json(contact); 141 | } catch (err) { 142 | console.log(err); 143 | } 144 | }); 145 | 146 | // add favourite 147 | app.put("/favourite", auth, async (req, res) => { 148 | const { contact_id, user_id, favourite } = req.body; 149 | 150 | try { 151 | const userId = await Contact.findOne({ user_id, contact_id }); 152 | 153 | if (userId) { 154 | const contact = await Contact.findByIdAndUpdate(contact_id, { 155 | favourite, 156 | }); 157 | 158 | return res.status(200).json(contact); 159 | } 160 | } catch (error) { 161 | console.log(error); 162 | } 163 | }); 164 | 165 | // get all contacts 166 | app.get("/fetch-contacts", auth, async (req, res) => { 167 | try { 168 | const { user_id } = req.query; 169 | 170 | const user = await Contact.findOne({ user_id }); 171 | 172 | const contact = await Contact.find({ user_id }); 173 | 174 | res.status(200).json(contact); 175 | } catch (err) { 176 | console.log(err); 177 | } 178 | }); 179 | 180 | // get all contacts 181 | app.get("/user-detail", auth, async (req, res) => { 182 | try { 183 | const { user_id } = req.query; 184 | 185 | const user = await User.findById(user_id); 186 | 187 | // Check if account exists 188 | if (!user) { 189 | return res.status(404).send({ detail: "User does not exist" }); 190 | } 191 | 192 | const userDetail = { 193 | id: user._id, 194 | full_name: user.full_name, 195 | email: user.email, 196 | favrite: user.favrite, 197 | }; 198 | 199 | res.status(200).json(userDetail); 200 | } catch (err) { 201 | console.log(err); 202 | } 203 | }); 204 | 205 | app.delete("/remove-contact", auth, async (req, res) => { 206 | const { _id, user_id } = req.query; 207 | 208 | const oldContact = await Contact.findOne({ _id, user_id }); 209 | 210 | // Check if account exists 211 | if (!oldContact) { 212 | return res.status(404).send({ detail: "User does not exist" }); 213 | } 214 | 215 | try { 216 | if (_id && user_id) { 217 | const contact = await Contact.deleteOne({ _id, user_id }); 218 | return res.status(200).json(contact); 219 | } 220 | } catch (error) { 221 | console.log(error); 222 | } 223 | }); 224 | 225 | app.put("/update-contact", auth, async (req, res) => { 226 | try { 227 | const { full_name, email, phone, address, user_id, _id, image } = req.body; 228 | 229 | const contactData = { full_name, email, phone, address, image }; 230 | 231 | if (!(email && full_name && phone && address)) { 232 | return res.status(400).send({ detail: "All input is required" }); 233 | } 234 | 235 | if (!image) { 236 | return res.status(400).send({ detail: "Please upload image" }); 237 | } 238 | 239 | const oldContact = await Contact.findOne({ 240 | ...contactData, 241 | }); 242 | 243 | if (oldContact) { 244 | return res.status(409).send({ detail: "Already Updated." }); 245 | } 246 | 247 | const contact = await Contact.findByIdAndUpdate(_id, contactData); 248 | 249 | res.status(200).json(contact); 250 | } catch (err) { 251 | console.log(err); 252 | } 253 | }); 254 | 255 | module.exports = app; 256 | -------------------------------------------------------------------------------- /server/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@sindresorhus/is@^0.14.0": 6 | version "0.14.0" 7 | resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" 8 | integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== 9 | 10 | "@szmarczak/http-timer@^1.1.2": 11 | version "1.1.2" 12 | resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" 13 | integrity sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA== 14 | dependencies: 15 | defer-to-connect "^1.0.1" 16 | 17 | "@types/node@*": 18 | version "17.0.8" 19 | resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.8.tgz#50d680c8a8a78fe30abe6906453b21ad8ab0ad7b" 20 | integrity sha512-YofkM6fGv4gDJq78g4j0mMuGMkZVxZDgtU0JRdx6FgiJDG+0fY0GKVolOV8WqVmEhLCXkQRjwDdKyPxJp/uucg== 21 | 22 | "@types/node@< 17.0.6": 23 | version "17.0.5" 24 | resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.5.tgz#57ca67ec4e57ad9e4ef5a6bab48a15387a1c83e0" 25 | integrity sha512-w3mrvNXLeDYV1GKTZorGJQivK6XLCoGwpnyJFbJVK/aTBQUxOCaa/GlFAAN3OTDFcb7h5tiFG+YXCO2By+riZw== 26 | 27 | "@types/webidl-conversions@*": 28 | version "6.1.1" 29 | resolved "https://registry.yarnpkg.com/@types/webidl-conversions/-/webidl-conversions-6.1.1.tgz#e33bc8ea812a01f63f90481c666334844b12a09e" 30 | integrity sha512-XAahCdThVuCFDQLT7R7Pk/vqeObFNL3YqRyFZg+AqAP/W1/w3xHaIxuW7WszQqTbIBOPRcItYJIou3i/mppu3Q== 31 | 32 | "@types/whatwg-url@^8.2.1": 33 | version "8.2.1" 34 | resolved "https://registry.yarnpkg.com/@types/whatwg-url/-/whatwg-url-8.2.1.tgz#f1aac222dab7c59e011663a0cb0a3117b2ef05d4" 35 | integrity sha512-2YubE1sjj5ifxievI5Ge1sckb9k/Er66HyR2c+3+I6VDUUg1TLPdYYTEbQ+DjRkS4nTxMJhgWfSfMRD2sl2EYQ== 36 | dependencies: 37 | "@types/node" "*" 38 | "@types/webidl-conversions" "*" 39 | 40 | abbrev@1: 41 | version "1.1.1" 42 | resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" 43 | integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== 44 | 45 | accepts@~1.3.7: 46 | version "1.3.7" 47 | resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" 48 | integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== 49 | dependencies: 50 | mime-types "~2.1.24" 51 | negotiator "0.6.2" 52 | 53 | ansi-align@^3.0.0: 54 | version "3.0.1" 55 | resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.1.tgz#0cdf12e111ace773a86e9a1fad1225c43cb19a59" 56 | integrity sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w== 57 | dependencies: 58 | string-width "^4.1.0" 59 | 60 | ansi-regex@^5.0.1: 61 | version "5.0.1" 62 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" 63 | integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== 64 | 65 | ansi-styles@^4.0.0, ansi-styles@^4.1.0: 66 | version "4.3.0" 67 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" 68 | integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== 69 | dependencies: 70 | color-convert "^2.0.1" 71 | 72 | anymatch@~3.1.2: 73 | version "3.1.2" 74 | resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" 75 | integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== 76 | dependencies: 77 | normalize-path "^3.0.0" 78 | picomatch "^2.0.4" 79 | 80 | array-flatten@1.1.1: 81 | version "1.1.1" 82 | resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" 83 | integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= 84 | 85 | balanced-match@^1.0.0: 86 | version "1.0.2" 87 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" 88 | integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== 89 | 90 | base64-js@^1.3.1: 91 | version "1.5.1" 92 | resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" 93 | integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== 94 | 95 | bcryptjs@^2.4.3: 96 | version "2.4.3" 97 | resolved "https://registry.yarnpkg.com/bcryptjs/-/bcryptjs-2.4.3.tgz#9ab5627b93e60621ff7cdac5da9733027df1d0cb" 98 | integrity sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms= 99 | 100 | binary-extensions@^2.0.0: 101 | version "2.2.0" 102 | resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" 103 | integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== 104 | 105 | body-parser@1.19.1, body-parser@^1.19.1: 106 | version "1.19.1" 107 | resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.1.tgz#1499abbaa9274af3ecc9f6f10396c995943e31d4" 108 | integrity sha512-8ljfQi5eBk8EJfECMrgqNGWPEY5jWP+1IzkzkGdFFEwFQZZyaZ21UqdaHktgiMlH0xLHqIFtE/u2OYE5dOtViA== 109 | dependencies: 110 | bytes "3.1.1" 111 | content-type "~1.0.4" 112 | debug "2.6.9" 113 | depd "~1.1.2" 114 | http-errors "1.8.1" 115 | iconv-lite "0.4.24" 116 | on-finished "~2.3.0" 117 | qs "6.9.6" 118 | raw-body "2.4.2" 119 | type-is "~1.6.18" 120 | 121 | boxen@^5.0.0: 122 | version "5.1.2" 123 | resolved "https://registry.yarnpkg.com/boxen/-/boxen-5.1.2.tgz#788cb686fc83c1f486dfa8a40c68fc2b831d2b50" 124 | integrity sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ== 125 | dependencies: 126 | ansi-align "^3.0.0" 127 | camelcase "^6.2.0" 128 | chalk "^4.1.0" 129 | cli-boxes "^2.2.1" 130 | string-width "^4.2.2" 131 | type-fest "^0.20.2" 132 | widest-line "^3.1.0" 133 | wrap-ansi "^7.0.0" 134 | 135 | brace-expansion@^1.1.7: 136 | version "1.1.11" 137 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 138 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== 139 | dependencies: 140 | balanced-match "^1.0.0" 141 | concat-map "0.0.1" 142 | 143 | braces@~3.0.2: 144 | version "3.0.2" 145 | resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" 146 | integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== 147 | dependencies: 148 | fill-range "^7.0.1" 149 | 150 | bson@^4.2.2, bson@^4.6.0: 151 | version "4.6.1" 152 | resolved "https://registry.yarnpkg.com/bson/-/bson-4.6.1.tgz#2b5da517539bb0f7f3ffb54ac70a384ca899641c" 153 | integrity sha512-I1LQ7Hz5zgwR4QquilLNZwbhPw0Apx7i7X9kGMBTsqPdml/03Q9NBtD9nt/19ahjlphktQImrnderxqpzeVDjw== 154 | dependencies: 155 | buffer "^5.6.0" 156 | 157 | buffer-equal-constant-time@1.0.1: 158 | version "1.0.1" 159 | resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" 160 | integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= 161 | 162 | buffer@^5.6.0: 163 | version "5.7.1" 164 | resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" 165 | integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== 166 | dependencies: 167 | base64-js "^1.3.1" 168 | ieee754 "^1.1.13" 169 | 170 | bytes@3.1.1: 171 | version "3.1.1" 172 | resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.1.tgz#3f018291cb4cbad9accb6e6970bca9c8889e879a" 173 | integrity sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg== 174 | 175 | cacheable-request@^6.0.0: 176 | version "6.1.0" 177 | resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912" 178 | integrity sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg== 179 | dependencies: 180 | clone-response "^1.0.2" 181 | get-stream "^5.1.0" 182 | http-cache-semantics "^4.0.0" 183 | keyv "^3.0.0" 184 | lowercase-keys "^2.0.0" 185 | normalize-url "^4.1.0" 186 | responselike "^1.0.2" 187 | 188 | camelcase@^6.2.0: 189 | version "6.3.0" 190 | resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" 191 | integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== 192 | 193 | chalk@^4.1.0: 194 | version "4.1.2" 195 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" 196 | integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== 197 | dependencies: 198 | ansi-styles "^4.1.0" 199 | supports-color "^7.1.0" 200 | 201 | chokidar@^3.5.2: 202 | version "3.5.2" 203 | resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75" 204 | integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ== 205 | dependencies: 206 | anymatch "~3.1.2" 207 | braces "~3.0.2" 208 | glob-parent "~5.1.2" 209 | is-binary-path "~2.1.0" 210 | is-glob "~4.0.1" 211 | normalize-path "~3.0.0" 212 | readdirp "~3.6.0" 213 | optionalDependencies: 214 | fsevents "~2.3.2" 215 | 216 | ci-info@^2.0.0: 217 | version "2.0.0" 218 | resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" 219 | integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== 220 | 221 | cli-boxes@^2.2.1: 222 | version "2.2.1" 223 | resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f" 224 | integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw== 225 | 226 | clone-response@^1.0.2: 227 | version "1.0.2" 228 | resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" 229 | integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws= 230 | dependencies: 231 | mimic-response "^1.0.0" 232 | 233 | color-convert@^2.0.1: 234 | version "2.0.1" 235 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" 236 | integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== 237 | dependencies: 238 | color-name "~1.1.4" 239 | 240 | color-name@~1.1.4: 241 | version "1.1.4" 242 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" 243 | integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== 244 | 245 | concat-map@0.0.1: 246 | version "0.0.1" 247 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 248 | integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= 249 | 250 | configstore@^5.0.1: 251 | version "5.0.1" 252 | resolved "https://registry.yarnpkg.com/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96" 253 | integrity sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA== 254 | dependencies: 255 | dot-prop "^5.2.0" 256 | graceful-fs "^4.1.2" 257 | make-dir "^3.0.0" 258 | unique-string "^2.0.0" 259 | write-file-atomic "^3.0.0" 260 | xdg-basedir "^4.0.0" 261 | 262 | content-disposition@0.5.4: 263 | version "0.5.4" 264 | resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" 265 | integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== 266 | dependencies: 267 | safe-buffer "5.2.1" 268 | 269 | content-type@~1.0.4: 270 | version "1.0.4" 271 | resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" 272 | integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== 273 | 274 | cookie-signature@1.0.6: 275 | version "1.0.6" 276 | resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" 277 | integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= 278 | 279 | cookie@0.4.1: 280 | version "0.4.1" 281 | resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1" 282 | integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA== 283 | 284 | cors@^2.8.5: 285 | version "2.8.5" 286 | resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" 287 | integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== 288 | dependencies: 289 | object-assign "^4" 290 | vary "^1" 291 | 292 | crypto-random-string@^2.0.0: 293 | version "2.0.0" 294 | resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" 295 | integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== 296 | 297 | debug@2.6.9: 298 | version "2.6.9" 299 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" 300 | integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== 301 | dependencies: 302 | ms "2.0.0" 303 | 304 | debug@4.x: 305 | version "4.3.3" 306 | resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" 307 | integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q== 308 | dependencies: 309 | ms "2.1.2" 310 | 311 | debug@^3.2.7: 312 | version "3.2.7" 313 | resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" 314 | integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== 315 | dependencies: 316 | ms "^2.1.1" 317 | 318 | decompress-response@^3.3.0: 319 | version "3.3.0" 320 | resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" 321 | integrity sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M= 322 | dependencies: 323 | mimic-response "^1.0.0" 324 | 325 | deep-extend@^0.6.0: 326 | version "0.6.0" 327 | resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" 328 | integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== 329 | 330 | defer-to-connect@^1.0.1: 331 | version "1.1.3" 332 | resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591" 333 | integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ== 334 | 335 | denque@^2.0.1: 336 | version "2.0.1" 337 | resolved "https://registry.yarnpkg.com/denque/-/denque-2.0.1.tgz#bcef4c1b80dc32efe97515744f21a4229ab8934a" 338 | integrity sha512-tfiWc6BQLXNLpNiR5iGd0Ocu3P3VpxfzFiqubLgMfhfOw9WyvgJBd46CClNn9k3qfbjvT//0cf7AlYRX/OslMQ== 339 | 340 | depd@~1.1.2: 341 | version "1.1.2" 342 | resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" 343 | integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= 344 | 345 | destroy@~1.0.4: 346 | version "1.0.4" 347 | resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" 348 | integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= 349 | 350 | dot-prop@^5.2.0: 351 | version "5.3.0" 352 | resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88" 353 | integrity sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q== 354 | dependencies: 355 | is-obj "^2.0.0" 356 | 357 | dotenv@^11.0.0: 358 | version "11.0.0" 359 | resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-11.0.0.tgz#ee37feddf8ada6d348a79e198312d4a8abfd1c1e" 360 | integrity sha512-Fp/b504Y5W+e+FpCxTFMUZ7ZEQkQYF0rx+KZtmwixJxGQbLHrhCwo3FjZgNC8vIfrSi29PABNbMoCGD9YoiXbQ== 361 | 362 | duplexer3@^0.1.4: 363 | version "0.1.4" 364 | resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" 365 | integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= 366 | 367 | ecdsa-sig-formatter@1.0.11: 368 | version "1.0.11" 369 | resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" 370 | integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== 371 | dependencies: 372 | safe-buffer "^5.0.1" 373 | 374 | ee-first@1.1.1: 375 | version "1.1.1" 376 | resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" 377 | integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= 378 | 379 | emoji-regex@^8.0.0: 380 | version "8.0.0" 381 | resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" 382 | integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== 383 | 384 | encodeurl@~1.0.2: 385 | version "1.0.2" 386 | resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" 387 | integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= 388 | 389 | end-of-stream@^1.1.0: 390 | version "1.4.4" 391 | resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" 392 | integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== 393 | dependencies: 394 | once "^1.4.0" 395 | 396 | escape-goat@^2.0.0: 397 | version "2.1.1" 398 | resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675" 399 | integrity sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q== 400 | 401 | escape-html@~1.0.3: 402 | version "1.0.3" 403 | resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" 404 | integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= 405 | 406 | etag@~1.8.1: 407 | version "1.8.1" 408 | resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" 409 | integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= 410 | 411 | express@^4.17.2: 412 | version "4.17.2" 413 | resolved "https://registry.yarnpkg.com/express/-/express-4.17.2.tgz#c18369f265297319beed4e5558753cc8c1364cb3" 414 | integrity sha512-oxlxJxcQlYwqPWKVJJtvQiwHgosH/LrLSPA+H4UxpyvSS6jC5aH+5MoHFM+KABgTOt0APue4w66Ha8jCUo9QGg== 415 | dependencies: 416 | accepts "~1.3.7" 417 | array-flatten "1.1.1" 418 | body-parser "1.19.1" 419 | content-disposition "0.5.4" 420 | content-type "~1.0.4" 421 | cookie "0.4.1" 422 | cookie-signature "1.0.6" 423 | debug "2.6.9" 424 | depd "~1.1.2" 425 | encodeurl "~1.0.2" 426 | escape-html "~1.0.3" 427 | etag "~1.8.1" 428 | finalhandler "~1.1.2" 429 | fresh "0.5.2" 430 | merge-descriptors "1.0.1" 431 | methods "~1.1.2" 432 | on-finished "~2.3.0" 433 | parseurl "~1.3.3" 434 | path-to-regexp "0.1.7" 435 | proxy-addr "~2.0.7" 436 | qs "6.9.6" 437 | range-parser "~1.2.1" 438 | safe-buffer "5.2.1" 439 | send "0.17.2" 440 | serve-static "1.14.2" 441 | setprototypeof "1.2.0" 442 | statuses "~1.5.0" 443 | type-is "~1.6.18" 444 | utils-merge "1.0.1" 445 | vary "~1.1.2" 446 | 447 | fill-range@^7.0.1: 448 | version "7.0.1" 449 | resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" 450 | integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== 451 | dependencies: 452 | to-regex-range "^5.0.1" 453 | 454 | finalhandler@~1.1.2: 455 | version "1.1.2" 456 | resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" 457 | integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== 458 | dependencies: 459 | debug "2.6.9" 460 | encodeurl "~1.0.2" 461 | escape-html "~1.0.3" 462 | on-finished "~2.3.0" 463 | parseurl "~1.3.3" 464 | statuses "~1.5.0" 465 | unpipe "~1.0.0" 466 | 467 | forwarded@0.2.0: 468 | version "0.2.0" 469 | resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" 470 | integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== 471 | 472 | fresh@0.5.2: 473 | version "0.5.2" 474 | resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" 475 | integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= 476 | 477 | fsevents@~2.3.2: 478 | version "2.3.2" 479 | resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" 480 | integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== 481 | 482 | get-stream@^4.1.0: 483 | version "4.1.0" 484 | resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" 485 | integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== 486 | dependencies: 487 | pump "^3.0.0" 488 | 489 | get-stream@^5.1.0: 490 | version "5.2.0" 491 | resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" 492 | integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== 493 | dependencies: 494 | pump "^3.0.0" 495 | 496 | glob-parent@~5.1.2: 497 | version "5.1.2" 498 | resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" 499 | integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== 500 | dependencies: 501 | is-glob "^4.0.1" 502 | 503 | global-dirs@^3.0.0: 504 | version "3.0.0" 505 | resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-3.0.0.tgz#70a76fe84ea315ab37b1f5576cbde7d48ef72686" 506 | integrity sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA== 507 | dependencies: 508 | ini "2.0.0" 509 | 510 | got@^9.6.0: 511 | version "9.6.0" 512 | resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" 513 | integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q== 514 | dependencies: 515 | "@sindresorhus/is" "^0.14.0" 516 | "@szmarczak/http-timer" "^1.1.2" 517 | cacheable-request "^6.0.0" 518 | decompress-response "^3.3.0" 519 | duplexer3 "^0.1.4" 520 | get-stream "^4.1.0" 521 | lowercase-keys "^1.0.1" 522 | mimic-response "^1.0.1" 523 | p-cancelable "^1.0.0" 524 | to-readable-stream "^1.0.0" 525 | url-parse-lax "^3.0.0" 526 | 527 | graceful-fs@^4.1.2: 528 | version "4.2.9" 529 | resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96" 530 | integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ== 531 | 532 | has-flag@^3.0.0: 533 | version "3.0.0" 534 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" 535 | integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= 536 | 537 | has-flag@^4.0.0: 538 | version "4.0.0" 539 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" 540 | integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== 541 | 542 | has-yarn@^2.1.0: 543 | version "2.1.0" 544 | resolved "https://registry.yarnpkg.com/has-yarn/-/has-yarn-2.1.0.tgz#137e11354a7b5bf11aa5cb649cf0c6f3ff2b2e77" 545 | integrity sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw== 546 | 547 | http-cache-semantics@^4.0.0: 548 | version "4.1.0" 549 | resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" 550 | integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== 551 | 552 | http-errors@1.8.1: 553 | version "1.8.1" 554 | resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.8.1.tgz#7c3f28577cbc8a207388455dbd62295ed07bd68c" 555 | integrity sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g== 556 | dependencies: 557 | depd "~1.1.2" 558 | inherits "2.0.4" 559 | setprototypeof "1.2.0" 560 | statuses ">= 1.5.0 < 2" 561 | toidentifier "1.0.1" 562 | 563 | iconv-lite@0.4.24: 564 | version "0.4.24" 565 | resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" 566 | integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== 567 | dependencies: 568 | safer-buffer ">= 2.1.2 < 3" 569 | 570 | ieee754@^1.1.13: 571 | version "1.2.1" 572 | resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" 573 | integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== 574 | 575 | ignore-by-default@^1.0.1: 576 | version "1.0.1" 577 | resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" 578 | integrity sha1-SMptcvbGo68Aqa1K5odr44ieKwk= 579 | 580 | import-lazy@^2.1.0: 581 | version "2.1.0" 582 | resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" 583 | integrity sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM= 584 | 585 | imurmurhash@^0.1.4: 586 | version "0.1.4" 587 | resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" 588 | integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= 589 | 590 | inherits@2.0.4: 591 | version "2.0.4" 592 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 593 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 594 | 595 | ini@2.0.0: 596 | version "2.0.0" 597 | resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5" 598 | integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== 599 | 600 | ini@~1.3.0: 601 | version "1.3.8" 602 | resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" 603 | integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== 604 | 605 | ipaddr.js@1.9.1: 606 | version "1.9.1" 607 | resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" 608 | integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== 609 | 610 | is-binary-path@~2.1.0: 611 | version "2.1.0" 612 | resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" 613 | integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== 614 | dependencies: 615 | binary-extensions "^2.0.0" 616 | 617 | is-ci@^2.0.0: 618 | version "2.0.0" 619 | resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" 620 | integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== 621 | dependencies: 622 | ci-info "^2.0.0" 623 | 624 | is-extglob@^2.1.1: 625 | version "2.1.1" 626 | resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" 627 | integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= 628 | 629 | is-fullwidth-code-point@^3.0.0: 630 | version "3.0.0" 631 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" 632 | integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== 633 | 634 | is-glob@^4.0.1, is-glob@~4.0.1: 635 | version "4.0.3" 636 | resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" 637 | integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== 638 | dependencies: 639 | is-extglob "^2.1.1" 640 | 641 | is-installed-globally@^0.4.0: 642 | version "0.4.0" 643 | resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.4.0.tgz#9a0fd407949c30f86eb6959ef1b7994ed0b7b520" 644 | integrity sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ== 645 | dependencies: 646 | global-dirs "^3.0.0" 647 | is-path-inside "^3.0.2" 648 | 649 | is-npm@^5.0.0: 650 | version "5.0.0" 651 | resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-5.0.0.tgz#43e8d65cc56e1b67f8d47262cf667099193f45a8" 652 | integrity sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA== 653 | 654 | is-number@^7.0.0: 655 | version "7.0.0" 656 | resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" 657 | integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== 658 | 659 | is-obj@^2.0.0: 660 | version "2.0.0" 661 | resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" 662 | integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== 663 | 664 | is-path-inside@^3.0.2: 665 | version "3.0.3" 666 | resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" 667 | integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== 668 | 669 | is-typedarray@^1.0.0: 670 | version "1.0.0" 671 | resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" 672 | integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= 673 | 674 | is-yarn-global@^0.3.0: 675 | version "0.3.0" 676 | resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232" 677 | integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw== 678 | 679 | json-buffer@3.0.0: 680 | version "3.0.0" 681 | resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" 682 | integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg= 683 | 684 | jsonwebtoken@^8.5.1: 685 | version "8.5.1" 686 | resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d" 687 | integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w== 688 | dependencies: 689 | jws "^3.2.2" 690 | lodash.includes "^4.3.0" 691 | lodash.isboolean "^3.0.3" 692 | lodash.isinteger "^4.0.4" 693 | lodash.isnumber "^3.0.3" 694 | lodash.isplainobject "^4.0.6" 695 | lodash.isstring "^4.0.1" 696 | lodash.once "^4.0.0" 697 | ms "^2.1.1" 698 | semver "^5.6.0" 699 | 700 | jwa@^1.4.1: 701 | version "1.4.1" 702 | resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" 703 | integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== 704 | dependencies: 705 | buffer-equal-constant-time "1.0.1" 706 | ecdsa-sig-formatter "1.0.11" 707 | safe-buffer "^5.0.1" 708 | 709 | jws@^3.2.2: 710 | version "3.2.2" 711 | resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" 712 | integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== 713 | dependencies: 714 | jwa "^1.4.1" 715 | safe-buffer "^5.0.1" 716 | 717 | kareem@2.3.3: 718 | version "2.3.3" 719 | resolved "https://registry.yarnpkg.com/kareem/-/kareem-2.3.3.tgz#a4432d7965a5bb06fc2b4eeae71317344c9a756a" 720 | integrity sha512-uESCXM2KdtOQ8LOvKyTUXEeg0MkYp4wGglTIpGcYHvjJcS5sn2Wkfrfit8m4xSbaNDAw2KdI9elgkOxZbrFYbg== 721 | 722 | keyv@^3.0.0: 723 | version "3.1.0" 724 | resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" 725 | integrity sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA== 726 | dependencies: 727 | json-buffer "3.0.0" 728 | 729 | latest-version@^5.1.0: 730 | version "5.1.0" 731 | resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face" 732 | integrity sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA== 733 | dependencies: 734 | package-json "^6.3.0" 735 | 736 | lodash.includes@^4.3.0: 737 | version "4.3.0" 738 | resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" 739 | integrity sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8= 740 | 741 | lodash.isboolean@^3.0.3: 742 | version "3.0.3" 743 | resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" 744 | integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY= 745 | 746 | lodash.isinteger@^4.0.4: 747 | version "4.0.4" 748 | resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" 749 | integrity sha1-YZwK89A/iwTDH1iChAt3sRzWg0M= 750 | 751 | lodash.isnumber@^3.0.3: 752 | version "3.0.3" 753 | resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" 754 | integrity sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w= 755 | 756 | lodash.isplainobject@^4.0.6: 757 | version "4.0.6" 758 | resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" 759 | integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs= 760 | 761 | lodash.isstring@^4.0.1: 762 | version "4.0.1" 763 | resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" 764 | integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE= 765 | 766 | lodash.once@^4.0.0: 767 | version "4.1.1" 768 | resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" 769 | integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= 770 | 771 | lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: 772 | version "1.0.1" 773 | resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" 774 | integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== 775 | 776 | lowercase-keys@^2.0.0: 777 | version "2.0.0" 778 | resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" 779 | integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== 780 | 781 | lru-cache@^6.0.0: 782 | version "6.0.0" 783 | resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" 784 | integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== 785 | dependencies: 786 | yallist "^4.0.0" 787 | 788 | make-dir@^3.0.0: 789 | version "3.1.0" 790 | resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" 791 | integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== 792 | dependencies: 793 | semver "^6.0.0" 794 | 795 | media-typer@0.3.0: 796 | version "0.3.0" 797 | resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" 798 | integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= 799 | 800 | memory-pager@^1.0.2: 801 | version "1.5.0" 802 | resolved "https://registry.yarnpkg.com/memory-pager/-/memory-pager-1.5.0.tgz#d8751655d22d384682741c972f2c3d6dfa3e66b5" 803 | integrity sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg== 804 | 805 | merge-descriptors@1.0.1: 806 | version "1.0.1" 807 | resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" 808 | integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= 809 | 810 | methods@~1.1.2: 811 | version "1.1.2" 812 | resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" 813 | integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= 814 | 815 | mime-db@1.51.0: 816 | version "1.51.0" 817 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.51.0.tgz#d9ff62451859b18342d960850dc3cfb77e63fb0c" 818 | integrity sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g== 819 | 820 | mime-types@~2.1.24: 821 | version "2.1.34" 822 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.34.tgz#5a712f9ec1503511a945803640fafe09d3793c24" 823 | integrity sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A== 824 | dependencies: 825 | mime-db "1.51.0" 826 | 827 | mime@1.6.0: 828 | version "1.6.0" 829 | resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" 830 | integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== 831 | 832 | mimic-response@^1.0.0, mimic-response@^1.0.1: 833 | version "1.0.1" 834 | resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" 835 | integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== 836 | 837 | minimatch@^3.0.4: 838 | version "3.0.4" 839 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" 840 | integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== 841 | dependencies: 842 | brace-expansion "^1.1.7" 843 | 844 | minimist@^1.2.0: 845 | version "1.2.5" 846 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" 847 | integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== 848 | 849 | mongodb-connection-string-url@^2.3.2: 850 | version "2.4.1" 851 | resolved "https://registry.yarnpkg.com/mongodb-connection-string-url/-/mongodb-connection-string-url-2.4.1.tgz#6b3c6c40133a0ad059fe9a0abda64b2a1cb4e8b4" 852 | integrity sha512-d5Kd2bVsKcSA7YI/yo57fSTtMwRQdFkvc5IZwod1RRxJtECeWPPSo7zqcUGJELifRA//Igs4spVtYAmvFCatug== 853 | dependencies: 854 | "@types/whatwg-url" "^8.2.1" 855 | whatwg-url "^11.0.0" 856 | 857 | mongodb@4.2.2: 858 | version "4.2.2" 859 | resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-4.2.2.tgz#cd70568bd96003877e35358ad17a0c5de35c6dfd" 860 | integrity sha512-zt8rCTnTKyMQppyt63qMnrLM5dbADgUk18ORPF1XbtHLIYCyc9hattaYHi0pqMvNxDpgGgUofSVzS+UQErgTug== 861 | dependencies: 862 | bson "^4.6.0" 863 | denque "^2.0.1" 864 | mongodb-connection-string-url "^2.3.2" 865 | optionalDependencies: 866 | saslprep "^1.0.3" 867 | 868 | mongoose@^6.1.6: 869 | version "6.1.6" 870 | resolved "https://registry.yarnpkg.com/mongoose/-/mongoose-6.1.6.tgz#47903ef821b63fc5ef80a259c2331cabf994c957" 871 | integrity sha512-wvNRJ547x/Bn3EMhwbVInNsEp2OdlMxk4Q/vfgAkw8OI+giZQ72S90ZH0H6VzzIhs4lCU9SwXHYa2O0sPicnEQ== 872 | dependencies: 873 | "@types/node" "< 17.0.6" 874 | bson "^4.2.2" 875 | kareem "2.3.3" 876 | mongodb "4.2.2" 877 | mpath "0.8.4" 878 | mquery "4.0.0" 879 | ms "2.1.2" 880 | regexp-clone "1.0.0" 881 | sift "13.5.2" 882 | sliced "1.0.1" 883 | 884 | mpath@0.8.4: 885 | version "0.8.4" 886 | resolved "https://registry.yarnpkg.com/mpath/-/mpath-0.8.4.tgz#6b566d9581621d9e931dd3b142ed3618e7599313" 887 | integrity sha512-DTxNZomBcTWlrMW76jy1wvV37X/cNNxPW1y2Jzd4DZkAaC5ZGsm8bfGfNOthcDuRJujXLqiuS6o3Tpy0JEoh7g== 888 | 889 | mquery@4.0.0: 890 | version "4.0.0" 891 | resolved "https://registry.yarnpkg.com/mquery/-/mquery-4.0.0.tgz#6c62160ad25289e99e0840907757cdfd62bde775" 892 | integrity sha512-nGjm89lHja+T/b8cybAby6H0YgA4qYC/lx6UlwvHGqvTq8bDaNeCwl1sY8uRELrNbVWJzIihxVd+vphGGn1vBw== 893 | dependencies: 894 | debug "4.x" 895 | regexp-clone "^1.0.0" 896 | sliced "1.0.1" 897 | 898 | ms@2.0.0: 899 | version "2.0.0" 900 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" 901 | integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= 902 | 903 | ms@2.1.2: 904 | version "2.1.2" 905 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" 906 | integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== 907 | 908 | ms@2.1.3, ms@^2.1.1: 909 | version "2.1.3" 910 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" 911 | integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== 912 | 913 | negotiator@0.6.2: 914 | version "0.6.2" 915 | resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" 916 | integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== 917 | 918 | nodemon@^2.0.15: 919 | version "2.0.15" 920 | resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.15.tgz#504516ce3b43d9dc9a955ccd9ec57550a31a8d4e" 921 | integrity sha512-gdHMNx47Gw7b3kWxJV64NI+Q5nfl0y5DgDbiVtShiwa7Z0IZ07Ll4RLFo6AjrhzMtoEZn5PDE3/c2AbVsiCkpA== 922 | dependencies: 923 | chokidar "^3.5.2" 924 | debug "^3.2.7" 925 | ignore-by-default "^1.0.1" 926 | minimatch "^3.0.4" 927 | pstree.remy "^1.1.8" 928 | semver "^5.7.1" 929 | supports-color "^5.5.0" 930 | touch "^3.1.0" 931 | undefsafe "^2.0.5" 932 | update-notifier "^5.1.0" 933 | 934 | nopt@~1.0.10: 935 | version "1.0.10" 936 | resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" 937 | integrity sha1-bd0hvSoxQXuScn3Vhfim83YI6+4= 938 | dependencies: 939 | abbrev "1" 940 | 941 | normalize-path@^3.0.0, normalize-path@~3.0.0: 942 | version "3.0.0" 943 | resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" 944 | integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== 945 | 946 | normalize-url@^4.1.0: 947 | version "4.5.1" 948 | resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a" 949 | integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA== 950 | 951 | object-assign@^4: 952 | version "4.1.1" 953 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" 954 | integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= 955 | 956 | on-finished@~2.3.0: 957 | version "2.3.0" 958 | resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" 959 | integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= 960 | dependencies: 961 | ee-first "1.1.1" 962 | 963 | once@^1.3.1, once@^1.4.0: 964 | version "1.4.0" 965 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 966 | integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= 967 | dependencies: 968 | wrappy "1" 969 | 970 | p-cancelable@^1.0.0: 971 | version "1.1.0" 972 | resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" 973 | integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw== 974 | 975 | package-json@^6.3.0: 976 | version "6.5.0" 977 | resolved "https://registry.yarnpkg.com/package-json/-/package-json-6.5.0.tgz#6feedaca35e75725876d0b0e64974697fed145b0" 978 | integrity sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ== 979 | dependencies: 980 | got "^9.6.0" 981 | registry-auth-token "^4.0.0" 982 | registry-url "^5.0.0" 983 | semver "^6.2.0" 984 | 985 | parseurl@~1.3.3: 986 | version "1.3.3" 987 | resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" 988 | integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== 989 | 990 | path-to-regexp@0.1.7: 991 | version "0.1.7" 992 | resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" 993 | integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= 994 | 995 | picomatch@^2.0.4, picomatch@^2.2.1: 996 | version "2.3.1" 997 | resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" 998 | integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== 999 | 1000 | prepend-http@^2.0.0: 1001 | version "2.0.0" 1002 | resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" 1003 | integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= 1004 | 1005 | proxy-addr@~2.0.7: 1006 | version "2.0.7" 1007 | resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" 1008 | integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== 1009 | dependencies: 1010 | forwarded "0.2.0" 1011 | ipaddr.js "1.9.1" 1012 | 1013 | pstree.remy@^1.1.8: 1014 | version "1.1.8" 1015 | resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a" 1016 | integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w== 1017 | 1018 | pump@^3.0.0: 1019 | version "3.0.0" 1020 | resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" 1021 | integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== 1022 | dependencies: 1023 | end-of-stream "^1.1.0" 1024 | once "^1.3.1" 1025 | 1026 | punycode@^2.1.1: 1027 | version "2.1.1" 1028 | resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" 1029 | integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== 1030 | 1031 | pupa@^2.1.1: 1032 | version "2.1.1" 1033 | resolved "https://registry.yarnpkg.com/pupa/-/pupa-2.1.1.tgz#f5e8fd4afc2c5d97828faa523549ed8744a20d62" 1034 | integrity sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A== 1035 | dependencies: 1036 | escape-goat "^2.0.0" 1037 | 1038 | qs@6.9.6: 1039 | version "6.9.6" 1040 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.6.tgz#26ed3c8243a431b2924aca84cc90471f35d5a0ee" 1041 | integrity sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ== 1042 | 1043 | range-parser@~1.2.1: 1044 | version "1.2.1" 1045 | resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" 1046 | integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== 1047 | 1048 | raw-body@2.4.2: 1049 | version "2.4.2" 1050 | resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.2.tgz#baf3e9c21eebced59dd6533ac872b71f7b61cb32" 1051 | integrity sha512-RPMAFUJP19WIet/99ngh6Iv8fzAbqum4Li7AD6DtGaW2RpMB/11xDoalPiJMTbu6I3hkbMVkATvZrqb9EEqeeQ== 1052 | dependencies: 1053 | bytes "3.1.1" 1054 | http-errors "1.8.1" 1055 | iconv-lite "0.4.24" 1056 | unpipe "1.0.0" 1057 | 1058 | rc@^1.2.8: 1059 | version "1.2.8" 1060 | resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" 1061 | integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== 1062 | dependencies: 1063 | deep-extend "^0.6.0" 1064 | ini "~1.3.0" 1065 | minimist "^1.2.0" 1066 | strip-json-comments "~2.0.1" 1067 | 1068 | readdirp@~3.6.0: 1069 | version "3.6.0" 1070 | resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" 1071 | integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== 1072 | dependencies: 1073 | picomatch "^2.2.1" 1074 | 1075 | regexp-clone@1.0.0, regexp-clone@^1.0.0: 1076 | version "1.0.0" 1077 | resolved "https://registry.yarnpkg.com/regexp-clone/-/regexp-clone-1.0.0.tgz#222db967623277056260b992626354a04ce9bf63" 1078 | integrity sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw== 1079 | 1080 | registry-auth-token@^4.0.0: 1081 | version "4.2.1" 1082 | resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-4.2.1.tgz#6d7b4006441918972ccd5fedcd41dc322c79b250" 1083 | integrity sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw== 1084 | dependencies: 1085 | rc "^1.2.8" 1086 | 1087 | registry-url@^5.0.0: 1088 | version "5.1.0" 1089 | resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-5.1.0.tgz#e98334b50d5434b81136b44ec638d9c2009c5009" 1090 | integrity sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw== 1091 | dependencies: 1092 | rc "^1.2.8" 1093 | 1094 | responselike@^1.0.2: 1095 | version "1.0.2" 1096 | resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" 1097 | integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec= 1098 | dependencies: 1099 | lowercase-keys "^1.0.0" 1100 | 1101 | safe-buffer@5.2.1, safe-buffer@^5.0.1: 1102 | version "5.2.1" 1103 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" 1104 | integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== 1105 | 1106 | "safer-buffer@>= 2.1.2 < 3": 1107 | version "2.1.2" 1108 | resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" 1109 | integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== 1110 | 1111 | saslprep@^1.0.3: 1112 | version "1.0.3" 1113 | resolved "https://registry.yarnpkg.com/saslprep/-/saslprep-1.0.3.tgz#4c02f946b56cf54297e347ba1093e7acac4cf226" 1114 | integrity sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag== 1115 | dependencies: 1116 | sparse-bitfield "^3.0.3" 1117 | 1118 | semver-diff@^3.1.1: 1119 | version "3.1.1" 1120 | resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-3.1.1.tgz#05f77ce59f325e00e2706afd67bb506ddb1ca32b" 1121 | integrity sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg== 1122 | dependencies: 1123 | semver "^6.3.0" 1124 | 1125 | semver@^5.6.0, semver@^5.7.1: 1126 | version "5.7.1" 1127 | resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" 1128 | integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== 1129 | 1130 | semver@^6.0.0, semver@^6.2.0, semver@^6.3.0: 1131 | version "6.3.0" 1132 | resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" 1133 | integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== 1134 | 1135 | semver@^7.3.4: 1136 | version "7.3.5" 1137 | resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" 1138 | integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== 1139 | dependencies: 1140 | lru-cache "^6.0.0" 1141 | 1142 | send@0.17.2: 1143 | version "0.17.2" 1144 | resolved "https://registry.yarnpkg.com/send/-/send-0.17.2.tgz#926622f76601c41808012c8bf1688fe3906f7820" 1145 | integrity sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww== 1146 | dependencies: 1147 | debug "2.6.9" 1148 | depd "~1.1.2" 1149 | destroy "~1.0.4" 1150 | encodeurl "~1.0.2" 1151 | escape-html "~1.0.3" 1152 | etag "~1.8.1" 1153 | fresh "0.5.2" 1154 | http-errors "1.8.1" 1155 | mime "1.6.0" 1156 | ms "2.1.3" 1157 | on-finished "~2.3.0" 1158 | range-parser "~1.2.1" 1159 | statuses "~1.5.0" 1160 | 1161 | serve-static@1.14.2: 1162 | version "1.14.2" 1163 | resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.2.tgz#722d6294b1d62626d41b43a013ece4598d292bfa" 1164 | integrity sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ== 1165 | dependencies: 1166 | encodeurl "~1.0.2" 1167 | escape-html "~1.0.3" 1168 | parseurl "~1.3.3" 1169 | send "0.17.2" 1170 | 1171 | setprototypeof@1.2.0: 1172 | version "1.2.0" 1173 | resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" 1174 | integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== 1175 | 1176 | sift@13.5.2: 1177 | version "13.5.2" 1178 | resolved "https://registry.yarnpkg.com/sift/-/sift-13.5.2.tgz#24a715e13c617b086166cd04917d204a591c9da6" 1179 | integrity sha512-+gxdEOMA2J+AI+fVsCqeNn7Tgx3M9ZN9jdi95939l1IJ8cZsqS8sqpJyOkic2SJk+1+98Uwryt/gL6XDaV+UZA== 1180 | 1181 | signal-exit@^3.0.2: 1182 | version "3.0.6" 1183 | resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.6.tgz#24e630c4b0f03fea446a2bd299e62b4a6ca8d0af" 1184 | integrity sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ== 1185 | 1186 | sliced@1.0.1: 1187 | version "1.0.1" 1188 | resolved "https://registry.yarnpkg.com/sliced/-/sliced-1.0.1.tgz#0b3a662b5d04c3177b1926bea82b03f837a2ef41" 1189 | integrity sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E= 1190 | 1191 | sparse-bitfield@^3.0.3: 1192 | version "3.0.3" 1193 | resolved "https://registry.yarnpkg.com/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz#ff4ae6e68656056ba4b3e792ab3334d38273ca11" 1194 | integrity sha1-/0rm5oZWBWuks+eSqzM004JzyhE= 1195 | dependencies: 1196 | memory-pager "^1.0.2" 1197 | 1198 | "statuses@>= 1.5.0 < 2", statuses@~1.5.0: 1199 | version "1.5.0" 1200 | resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" 1201 | integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= 1202 | 1203 | string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.2: 1204 | version "4.2.3" 1205 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" 1206 | integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== 1207 | dependencies: 1208 | emoji-regex "^8.0.0" 1209 | is-fullwidth-code-point "^3.0.0" 1210 | strip-ansi "^6.0.1" 1211 | 1212 | strip-ansi@^6.0.0, strip-ansi@^6.0.1: 1213 | version "6.0.1" 1214 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" 1215 | integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== 1216 | dependencies: 1217 | ansi-regex "^5.0.1" 1218 | 1219 | strip-json-comments@~2.0.1: 1220 | version "2.0.1" 1221 | resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" 1222 | integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= 1223 | 1224 | supports-color@^5.5.0: 1225 | version "5.5.0" 1226 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" 1227 | integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== 1228 | dependencies: 1229 | has-flag "^3.0.0" 1230 | 1231 | supports-color@^7.1.0: 1232 | version "7.2.0" 1233 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" 1234 | integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== 1235 | dependencies: 1236 | has-flag "^4.0.0" 1237 | 1238 | to-readable-stream@^1.0.0: 1239 | version "1.0.0" 1240 | resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771" 1241 | integrity sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q== 1242 | 1243 | to-regex-range@^5.0.1: 1244 | version "5.0.1" 1245 | resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" 1246 | integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== 1247 | dependencies: 1248 | is-number "^7.0.0" 1249 | 1250 | toidentifier@1.0.1: 1251 | version "1.0.1" 1252 | resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" 1253 | integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== 1254 | 1255 | touch@^3.1.0: 1256 | version "3.1.0" 1257 | resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b" 1258 | integrity sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA== 1259 | dependencies: 1260 | nopt "~1.0.10" 1261 | 1262 | tr46@^3.0.0: 1263 | version "3.0.0" 1264 | resolved "https://registry.yarnpkg.com/tr46/-/tr46-3.0.0.tgz#555c4e297a950617e8eeddef633c87d4d9d6cbf9" 1265 | integrity sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA== 1266 | dependencies: 1267 | punycode "^2.1.1" 1268 | 1269 | type-fest@^0.20.2: 1270 | version "0.20.2" 1271 | resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" 1272 | integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== 1273 | 1274 | type-is@~1.6.18: 1275 | version "1.6.18" 1276 | resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" 1277 | integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== 1278 | dependencies: 1279 | media-typer "0.3.0" 1280 | mime-types "~2.1.24" 1281 | 1282 | typedarray-to-buffer@^3.1.5: 1283 | version "3.1.5" 1284 | resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" 1285 | integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== 1286 | dependencies: 1287 | is-typedarray "^1.0.0" 1288 | 1289 | undefsafe@^2.0.5: 1290 | version "2.0.5" 1291 | resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" 1292 | integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== 1293 | 1294 | unique-string@^2.0.0: 1295 | version "2.0.0" 1296 | resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" 1297 | integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg== 1298 | dependencies: 1299 | crypto-random-string "^2.0.0" 1300 | 1301 | unpipe@1.0.0, unpipe@~1.0.0: 1302 | version "1.0.0" 1303 | resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" 1304 | integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= 1305 | 1306 | update-notifier@^5.1.0: 1307 | version "5.1.0" 1308 | resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-5.1.0.tgz#4ab0d7c7f36a231dd7316cf7729313f0214d9ad9" 1309 | integrity sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw== 1310 | dependencies: 1311 | boxen "^5.0.0" 1312 | chalk "^4.1.0" 1313 | configstore "^5.0.1" 1314 | has-yarn "^2.1.0" 1315 | import-lazy "^2.1.0" 1316 | is-ci "^2.0.0" 1317 | is-installed-globally "^0.4.0" 1318 | is-npm "^5.0.0" 1319 | is-yarn-global "^0.3.0" 1320 | latest-version "^5.1.0" 1321 | pupa "^2.1.1" 1322 | semver "^7.3.4" 1323 | semver-diff "^3.1.1" 1324 | xdg-basedir "^4.0.0" 1325 | 1326 | url-parse-lax@^3.0.0: 1327 | version "3.0.0" 1328 | resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c" 1329 | integrity sha1-FrXK/Afb42dsGxmZF3gj1lA6yww= 1330 | dependencies: 1331 | prepend-http "^2.0.0" 1332 | 1333 | utils-merge@1.0.1: 1334 | version "1.0.1" 1335 | resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" 1336 | integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= 1337 | 1338 | vary@^1, vary@~1.1.2: 1339 | version "1.1.2" 1340 | resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" 1341 | integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= 1342 | 1343 | webidl-conversions@^7.0.0: 1344 | version "7.0.0" 1345 | resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" 1346 | integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g== 1347 | 1348 | whatwg-url@^11.0.0: 1349 | version "11.0.0" 1350 | resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-11.0.0.tgz#0a849eebb5faf2119b901bb76fd795c2848d4018" 1351 | integrity sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ== 1352 | dependencies: 1353 | tr46 "^3.0.0" 1354 | webidl-conversions "^7.0.0" 1355 | 1356 | widest-line@^3.1.0: 1357 | version "3.1.0" 1358 | resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca" 1359 | integrity sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg== 1360 | dependencies: 1361 | string-width "^4.0.0" 1362 | 1363 | wrap-ansi@^7.0.0: 1364 | version "7.0.0" 1365 | resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" 1366 | integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== 1367 | dependencies: 1368 | ansi-styles "^4.0.0" 1369 | string-width "^4.1.0" 1370 | strip-ansi "^6.0.0" 1371 | 1372 | wrappy@1: 1373 | version "1.0.2" 1374 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 1375 | integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= 1376 | 1377 | write-file-atomic@^3.0.0: 1378 | version "3.0.3" 1379 | resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" 1380 | integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== 1381 | dependencies: 1382 | imurmurhash "^0.1.4" 1383 | is-typedarray "^1.0.0" 1384 | signal-exit "^3.0.2" 1385 | typedarray-to-buffer "^3.1.5" 1386 | 1387 | xdg-basedir@^4.0.0: 1388 | version "4.0.0" 1389 | resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" 1390 | integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== 1391 | 1392 | yallist@^4.0.0: 1393 | version "4.0.0" 1394 | resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" 1395 | integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== 1396 | --------------------------------------------------------------------------------