├── .gitignore ├── client ├── public │ ├── robots.txt │ ├── favicon.ico │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── index.html ├── src │ ├── Images │ │ ├── memories.jpg │ │ └── child_walk_forest_128464_2560x1600.jpg │ ├── Components │ │ ├── Home │ │ │ ├── Home.module.scss │ │ │ └── Home.jsx │ │ ├── Posts │ │ │ ├── Post │ │ │ │ ├── Post.module.scss │ │ │ │ └── Post.jsx │ │ │ └── Posts.jsx │ │ ├── Authentication │ │ │ ├── Authentication.module.scss │ │ │ └── Authentication.jsx │ │ ├── ProtectedRoute │ │ │ └── ProtectedRoute.jsx │ │ ├── ChipInput │ │ │ └── ChipInput.jsx │ │ ├── Navigationbar │ │ │ └── Navigationbar.jsx │ │ ├── SearchPost │ │ │ └── SearchPost.jsx │ │ ├── AddPostForm │ │ │ └── AddPostForm.jsx │ │ └── PostDetails │ │ │ └── PostDetails.jsx │ ├── setupTests.js │ ├── App.test.js │ ├── Redux │ │ ├── Reducers │ │ │ ├── postDetailsReducer.jsx │ │ │ ├── postIdReducer.jsx │ │ │ ├── postCountReducer.jsx │ │ │ ├── index.jsx │ │ │ ├── googleAuthReducer.jsx │ │ │ ├── errorReducer.jsx │ │ │ └── postReducer.jsx │ │ ├── Store │ │ │ └── store.jsx │ │ └── Actions │ │ │ ├── actionStrings.jsx │ │ │ └── actions.jsx │ ├── reportWebVitals.js │ ├── App.scss │ ├── index.js │ ├── logo.svg │ ├── App.js │ ├── index.css │ └── APIs │ │ └── APIs.js ├── .vscode │ └── launch.json ├── package.json └── README.md ├── server ├── example.env ├── Common │ ├── Enum │ │ └── roles.js │ ├── Rbac │ │ └── rbac.js │ └── Middlewares │ │ ├── requestValidation.js │ │ └── isAuthorized.js ├── modules │ ├── users │ │ ├── user-endpoints.js │ │ ├── controller │ │ │ ├── services.js │ │ │ └── user-control.js │ │ ├── model │ │ │ └── user-model.js │ │ ├── routes │ │ │ └── user-routes.js │ │ └── joi │ │ │ └── user-joi.js │ └── posts │ │ ├── post-endpoints.js │ │ ├── model │ │ └── post-model.js │ │ ├── routes │ │ └── post-routes.js │ │ ├── joi │ │ └── post-joi.js │ │ └── controller │ │ └── post-control.js ├── .vscode │ └── launch.json ├── package.json ├── Configration │ └── configDB.js ├── index.js └── yarn.lock └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | client/node_modules 2 | server/node_modules 3 | .env -------------------------------------------------------------------------------- /client/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /server/example.env: -------------------------------------------------------------------------------- 1 | PROT = {port} 2 | CONNECTION_STRING = {mongodb url} 3 | ENCRYPT_KEY = {bcrypt key} 4 | -------------------------------------------------------------------------------- /client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ahmedayman1420/memories/HEAD/client/public/favicon.ico -------------------------------------------------------------------------------- /client/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ahmedayman1420/memories/HEAD/client/public/logo192.png -------------------------------------------------------------------------------- /client/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ahmedayman1420/memories/HEAD/client/public/logo512.png -------------------------------------------------------------------------------- /client/src/Images/memories.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ahmedayman1420/memories/HEAD/client/src/Images/memories.jpg -------------------------------------------------------------------------------- /client/src/Components/Home/Home.module.scss: -------------------------------------------------------------------------------- 1 | .titleContainer { 2 | border-radius: 15px; 3 | box-shadow: 5px 5px 50px black; 4 | } 5 | -------------------------------------------------------------------------------- /client/src/Images/child_walk_forest_128464_2560x1600.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ahmedayman1420/memories/HEAD/client/src/Images/child_walk_forest_128464_2560x1600.jpg -------------------------------------------------------------------------------- /client/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /client/src/App.test.js: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react'; 2 | import App from './App'; 3 | 4 | test('renders learn react link', () => { 5 | render(); 6 | const linkElement = screen.getByText(/learn react/i); 7 | expect(linkElement).toBeInTheDocument(); 8 | }); 9 | -------------------------------------------------------------------------------- /client/src/Components/Posts/Post/Post.module.scss: -------------------------------------------------------------------------------- 1 | .icon:hover { 2 | cursor: pointer; 3 | transform: scale(1.2); 4 | } 5 | 6 | .cardParent { 7 | position: relative !important; 8 | } 9 | 10 | .cardChild { 11 | position: absolute; 12 | top: 2%; 13 | left: 2%; 14 | width: 98%; 15 | } 16 | -------------------------------------------------------------------------------- /server/Common/Enum/roles.js: -------------------------------------------------------------------------------- 1 | // ====== --- ====== >User module roles < ====== --- ====== // 2 | const roles = Object.freeze({ 3 | SUPER_ADMIN: "superAdmin", 4 | ADMIN: "admin", 5 | USER: "user", 6 | }); 7 | 8 | // ====== --- ====== > Exports user roles < ====== --- ====== // 9 | module.exports = roles; 10 | -------------------------------------------------------------------------------- /server/modules/users/user-endpoints.js: -------------------------------------------------------------------------------- 1 | // ====== --- ====== > User module endpoints < ====== --- ====== // 2 | const updateUserPassword = "User:updateUserPassword"; 3 | 4 | const userEndpoints = { 5 | updateUserPassword, 6 | }; 7 | 8 | // ====== --- ====== > Export user endpoints < ====== --- ====== // 9 | module.exports = userEndpoints; -------------------------------------------------------------------------------- /client/src/Redux/Reducers/postDetailsReducer.jsx: -------------------------------------------------------------------------------- 1 | import { SET_POST_DETAILS } from "../Actions/actionStrings"; 2 | 3 | const postDetailsReducer = (state = {}, action) => { 4 | switch (action.type) { 5 | case SET_POST_DETAILS: 6 | return action.payload; 7 | 8 | default: 9 | return state; 10 | } 11 | }; 12 | 13 | export default postDetailsReducer; 14 | -------------------------------------------------------------------------------- /client/src/Redux/Reducers/postIdReducer.jsx: -------------------------------------------------------------------------------- 1 | import { EDIT_POST_ID, RESET_ID } from "../Actions/actionStrings"; 2 | 3 | const postIdReducer = (state = "", action) => { 4 | switch (action.type) { 5 | case EDIT_POST_ID: 6 | return action.payload; 7 | 8 | case RESET_ID: 9 | return ""; 10 | 11 | default: 12 | return state; 13 | } 14 | }; 15 | 16 | export default postIdReducer; 17 | -------------------------------------------------------------------------------- /server/modules/users/controller/services.js: -------------------------------------------------------------------------------- 1 | function generatePassword() { 2 | var length = 8, 3 | charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", 4 | retVal = ""; 5 | for (var i = 0, n = charset.length; i < length; ++i) { 6 | retVal += charset.charAt(Math.floor(Math.random() * n)); 7 | } 8 | return retVal; 9 | } 10 | 11 | module.exports = { generatePassword }; 12 | -------------------------------------------------------------------------------- /client/src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /client/src/Redux/Store/store.jsx: -------------------------------------------------------------------------------- 1 | import { applyMiddleware, compose, createStore } from "redux"; 2 | import thunk from "redux-thunk"; 3 | import reducers from "../Reducers"; 4 | 5 | const initialState = { 6 | posts: [], 7 | error: { value: false, message: "" }, 8 | postId: "", 9 | postCount: 0, 10 | postDetails: {}, 11 | }; 12 | 13 | export const store = createStore( 14 | reducers, 15 | initialState, 16 | compose(applyMiddleware(thunk)) 17 | ); 18 | -------------------------------------------------------------------------------- /client/src/Components/Authentication/Authentication.module.scss: -------------------------------------------------------------------------------- 1 | .sign:hover { 2 | cursor: pointer; 3 | text-decoration: underline; 4 | } 5 | 6 | .password, 7 | .confirmPasswrod { 8 | position: relative !important; 9 | } 10 | .posswordIcon, 11 | .confirmPasswrodIcon { 12 | position: absolute !important; 13 | top: 50%; 14 | transform: translateY(-50%); 15 | right: 2%; 16 | } 17 | 18 | .posswordIcon:hover, 19 | .confirmPasswrodIcon:hover { 20 | cursor: pointer; 21 | } 22 | -------------------------------------------------------------------------------- /client/src/Components/ProtectedRoute/ProtectedRoute.jsx: -------------------------------------------------------------------------------- 1 | import { Outlet, Navigate } from "react-router-dom"; 2 | import jwt_decode from "jwt-decode"; 3 | 4 | const ProtectedRoute = () => { 5 | const memoryProfile = JSON.parse(localStorage.getItem("memoryProfile")); 6 | let auth = false; 7 | try { 8 | var decoded = jwt_decode(memoryProfile.token); 9 | auth = true; 10 | } catch (error) { 11 | auth = false; 12 | } 13 | return auth ? : ; 14 | }; 15 | 16 | export default ProtectedRoute; 17 | -------------------------------------------------------------------------------- /client/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Launch Chrome", 9 | "request": "launch", 10 | "type": "chrome", 11 | "url": "http://localhost:3000", 12 | "webRoot": "${workspaceFolder}" 13 | }, 14 | ] 15 | } -------------------------------------------------------------------------------- /client/src/Redux/Reducers/postCountReducer.jsx: -------------------------------------------------------------------------------- 1 | import { 2 | DECREASE_POST_COUNT, 3 | INCREASE_POST_COUNT, 4 | SET_POST_COUNT, 5 | } from "../Actions/actionStrings"; 6 | 7 | const postCountReducer = (state = 0, action) => { 8 | switch (action.type) { 9 | case SET_POST_COUNT: 10 | return action.payload; 11 | 12 | case INCREASE_POST_COUNT: 13 | return state + 1; 14 | 15 | case DECREASE_POST_COUNT: 16 | return state - 1; 17 | 18 | default: 19 | return state; 20 | } 21 | }; 22 | 23 | export default postCountReducer; 24 | -------------------------------------------------------------------------------- /server/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "pwa-chrome", 9 | "request": "launch", 10 | "name": "Launch Chrome against localhost", 11 | "url": "http://localhost:8080", 12 | "webRoot": "${workspaceFolder}" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /client/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /server/modules/posts/post-endpoints.js: -------------------------------------------------------------------------------- 1 | // ====== --- ====== > Post module endpoints < ====== --- ====== // 2 | const AddPost = "Post:AddPost"; 3 | const editPost = "Post:editPost"; 4 | const deletePost = "Post:deletePost"; 5 | const likePost = "Post:likePost"; 6 | const searchPost = "Post:searchPost"; 7 | const getPost = "Post:getPost"; 8 | const addComment = "Post:addComment"; 9 | 10 | const postEndpoints = { 11 | AddPost, 12 | editPost, 13 | deletePost, 14 | likePost, 15 | searchPost, 16 | getPost, 17 | addComment, 18 | }; 19 | 20 | // ====== --- ====== > Export post endpoints < ====== --- ====== // 21 | module.exports = postEndpoints; 22 | -------------------------------------------------------------------------------- /client/src/Redux/Reducers/index.jsx: -------------------------------------------------------------------------------- 1 | import { combineReducers } from "redux"; 2 | import errorReducer from "./errorReducer"; 3 | import googleAuthReducer from "./googleAuthReducer"; 4 | import postCountReducer from "./postCountReducer"; 5 | import postDetailsReducer from "./postDetailsReducer"; 6 | import postIdReducer from "./postIdReducer"; 7 | import postReducer from "./postReducer"; 8 | 9 | const reducers = combineReducers({ 10 | posts: postReducer, 11 | error: errorReducer, 12 | postId: postIdReducer, 13 | googleAuth: googleAuthReducer, 14 | postCount: postCountReducer, 15 | postDetails: postDetailsReducer, 16 | }); 17 | 18 | export default reducers; 19 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "1--express-server", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node index.js" 8 | }, 9 | "keywords": [], 10 | "author": "Ahmed Ayman Ahmed Ibrahim", 11 | "license": "ISC", 12 | "dependencies": { 13 | "bcrypt": "^5.0.1", 14 | "body-parse": "^0.1.0", 15 | "cors": "^2.8.5", 16 | "dotenv": "^16.0.0", 17 | "easy-rbac": "^3.2.0", 18 | "express": "^4.17.3", 19 | "http-status-codes": "^2.2.0", 20 | "joi": "^17.6.0", 21 | "jsonwebtoken": "^8.5.1", 22 | "mongoose": "^6.3.0", 23 | "rbac": "^5.0.3" 24 | }, 25 | "devDependencies": { 26 | "nodemon": "^2.0.15" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /client/src/App.scss: -------------------------------------------------------------------------------- 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 | 32 | 33 | @keyframes App-logo-spin { 34 | from { 35 | transform: rotate(0deg); 36 | } 37 | to { 38 | transform: rotate(360deg); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /server/Configration/configDB.js: -------------------------------------------------------------------------------- 1 | /* ====================== /// <==> Variables Declaration <==> /// ====================== */ 2 | const mongoose = require('mongoose'); 3 | 4 | /* ====================== /// <==> Variables Declaration <==> /// ====================== */ 5 | /* 6 | //==// connection function used to connection node.js project with mongodb 7 | */ 8 | const Connection = async() => { 9 | await mongoose.connect(`${process.env.CONNECTION_STRING}`, {}).then( 10 | (result) => { console.log('Node Connected With Mongo BD'); }).catch( 11 | (error) => { console.log('Error In Database Connection'); } 12 | ); 13 | }; 14 | 15 | /* ====================== /// <==> Export Connection Function <==> /// ====================== */ 16 | module.exports = Connection; -------------------------------------------------------------------------------- /client/src/Components/ChipInput/ChipInput.jsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import ReactChipInput from "react-chip-input"; 4 | 5 | export class ChipInput extends React.Component { 6 | state = { 7 | chips: [], 8 | }; 9 | addChip = (value) => { 10 | const chips = this.state.chips.slice(); 11 | chips.push(value); 12 | this.setState({ chips }); 13 | }; 14 | removeChip = (index) => { 15 | const chips = this.state.chips.slice(); 16 | chips.splice(index, 1); 17 | this.setState({ chips }); 18 | }; 19 | render() { 20 | return ( 21 | this.addChip(value)} 25 | onRemove={(index) => this.removeChip(index)} 26 | onChange={this.props.savePostData} 27 | name="chipinput1" 28 | /> 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /client/src/Components/Home/Home.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import AddPostForm from "../AddPostForm/AddPostForm"; 3 | import Style from "./Home.module.scss"; 4 | import Posts from "../Posts/Posts"; 5 | import SearchPost from "../SearchPost/SearchPost"; 6 | 7 | function Home() { 8 | return ( 9 | <> 10 |
11 |
12 |
13 |
14 |
15 | 16 |
17 |
18 | 19 |
20 |
21 | 22 |
23 |
24 |
25 | 26 | ); 27 | } 28 | 29 | export default Home; 30 | -------------------------------------------------------------------------------- /client/src/Redux/Reducers/googleAuthReducer.jsx: -------------------------------------------------------------------------------- 1 | import { 2 | GOOGLE_AUTH, 3 | LOGOUT, 4 | SIGNIN, 5 | SIGNUP, 6 | UPDATE_GOOGLE_AUTH, 7 | } from "../Actions/actionStrings"; 8 | 9 | const googleAuthReducer = (state = {}, action) => { 10 | switch (action.type) { 11 | case GOOGLE_AUTH: 12 | localStorage.setItem("memoryProfile", JSON.stringify(action.payload)); 13 | return action.payload; 14 | 15 | case UPDATE_GOOGLE_AUTH: 16 | return action.payload; 17 | 18 | case SIGNUP: 19 | localStorage.setItem("memoryProfile", JSON.stringify(action.payload)); 20 | return action.payload; 21 | 22 | case SIGNIN: 23 | localStorage.setItem("memoryProfile", JSON.stringify(action.payload)); 24 | return action.payload; 25 | 26 | case LOGOUT: 27 | localStorage.clear(); 28 | return {}; 29 | 30 | default: 31 | return state; 32 | } 33 | }; 34 | 35 | export default googleAuthReducer; 36 | -------------------------------------------------------------------------------- /client/src/Redux/Reducers/errorReducer.jsx: -------------------------------------------------------------------------------- 1 | import { 2 | ERROR_ADD_POST, 3 | ERROR_EDIT_POST, 4 | ERROR_RESET, 5 | ERROR_SEARCH_POST, 6 | ERROR_SEND_COMMENT, 7 | ERROR_SIGNIN, 8 | ERROR_SIGNUP, 9 | } from "../Actions/actionStrings"; 10 | 11 | const errorReducer = (state = { value: false, message: "" }, action) => { 12 | switch (action.type) { 13 | case ERROR_ADD_POST: 14 | return { ...action.payload, type: "post" }; 15 | 16 | case ERROR_EDIT_POST: 17 | return { ...action.payload, type: "post" }; 18 | 19 | case ERROR_SIGNUP: 20 | return { ...action.payload, type: "auth" }; 21 | 22 | case ERROR_SIGNIN: 23 | return { ...action.payload, type: "auth" }; 24 | 25 | case ERROR_SEARCH_POST: 26 | return { ...action.payload, type: "search" }; 27 | 28 | case ERROR_SEND_COMMENT: 29 | return { ...action.payload, type: "comment" }; 30 | 31 | case ERROR_RESET: 32 | return action.payload; 33 | 34 | default: 35 | return state; 36 | } 37 | }; 38 | 39 | export default errorReducer; 40 | -------------------------------------------------------------------------------- /client/src/Redux/Reducers/postReducer.jsx: -------------------------------------------------------------------------------- 1 | import { 2 | ADD_POST, 3 | DELETE_POST, 4 | EDIT_POST, 5 | GET_POSTS, 6 | LIKE_POST, 7 | SEARCH_POST, 8 | } from "../Actions/actionStrings"; 9 | 10 | const postReducer = (state = [], action) => { 11 | switch (action.type) { 12 | case ADD_POST: 13 | return [action.payload, state[0]]; 14 | 15 | case EDIT_POST: 16 | return state.map((obj) => { 17 | if (action.payload._id === obj._id) return action.payload; 18 | else return obj; 19 | }); 20 | 21 | case LIKE_POST: 22 | return state.map((obj) => { 23 | if (action.payload._id === obj._id) return action.payload; 24 | else return obj; 25 | }); 26 | 27 | case DELETE_POST: 28 | return state.filter((item) => { 29 | return item._id !== action.payload; 30 | }); 31 | 32 | case GET_POSTS: 33 | return action.payload; 34 | 35 | case SEARCH_POST: 36 | return action.payload; 37 | 38 | default: 39 | return state; 40 | } 41 | }; 42 | 43 | export default postReducer; 44 | -------------------------------------------------------------------------------- /client/src/Redux/Actions/actionStrings.jsx: -------------------------------------------------------------------------------- 1 | export const ADD_POST = "ADD_POST"; 2 | export const EDIT_POST = "EDIT_POST"; 3 | export const LIKE_POST = "LIKE_POST"; 4 | export const DELETE_POST = "DELETE_POST"; 5 | export const GET_POSTS = "GET_POSTS"; 6 | export const ERROR_ADD_POST = "ERROR_ADD_POST"; 7 | export const ERROR_SEARCH_POST = "ERROR_SEARCH_POST"; 8 | export const ERROR_EDIT_POST = "ERROR_EDIT_POST"; 9 | export const SEARCH_POST = "SEARCH_POST"; 10 | export const ERROR_SIGNUP = "ERROR_SIGNUP"; 11 | export const ERROR_SIGNIN = "ERROR_SIGNIN"; 12 | export const ERROR_SEND_COMMENT = "ERROR_SEND_COMMENT"; 13 | export const ERROR_RESET = "ERROR_RESET"; 14 | export const EDIT_POST_ID = "EDIT_POST_ID"; 15 | export const RESET_ID = "RESET_ID"; 16 | export const SIGNUP = "SIGNUP"; 17 | export const SIGNIN = "SIGNIN"; 18 | export const GOOGLE_AUTH = "GOOGLE_AUTH"; 19 | export const UPDATE_GOOGLE_AUTH = "UPDATE_GOOGLE_AUTH"; 20 | export const LOGOUT = "LOGOUT"; 21 | export const INCREASE_POST_COUNT = "INCREASE_POST_COUNT"; 22 | export const DECREASE_POST_COUNT = "DECREASE_POST_COUNT"; 23 | export const SET_POST_COUNT = "SET_POST_COUNT"; 24 | export const SET_POST_DETAILS = "SET_POST_DETAILS"; 25 | -------------------------------------------------------------------------------- /server/Common/Rbac/rbac.js: -------------------------------------------------------------------------------- 1 | // ====== --- ====== > Modules endpoints < ====== --- ====== // 2 | const Rbac = require("easy-rbac"); 3 | const postEndpoints = require("../../modules/posts/post-endpoints"); 4 | const userEndpoints = require("../../modules/users/user-endpoints"); 5 | const roles = require("../Enum/roles"); 6 | 7 | // ====== --- ====== > Roles policies < ====== --- ====== // 8 | const userPolicies = [ 9 | postEndpoints.AddPost, 10 | postEndpoints.editPost, 11 | postEndpoints.deletePost, 12 | postEndpoints.likePost, 13 | postEndpoints.searchPost, 14 | postEndpoints.getPost, 15 | postEndpoints.addComment, 16 | ]; 17 | const adminPolicies = []; 18 | const superAdminPolicies = []; 19 | 20 | // ====== --- ====== > Match Between Roles & Them EndPoints < ====== --- ====== // 21 | const opts = { 22 | [roles.USER]: { 23 | can: userPolicies, 24 | }, 25 | [roles.ADMIN]: { 26 | can: adminPolicies, 27 | inherits: [roles.USER], 28 | }, 29 | [roles.SUPER_ADMIN]: { 30 | can: superAdminPolicies, 31 | inherits: [roles.ADMIN, roles.USER], 32 | }, 33 | }; 34 | 35 | // ====== --- ====== > Create rbac of user module < ====== --- ====== // 36 | userRbac = Rbac.create(opts); 37 | 38 | // ====== --- ====== > Exports userRabac < ====== --- ====== // 39 | module.exports = userRbac; 40 | -------------------------------------------------------------------------------- /server/modules/users/model/user-model.js: -------------------------------------------------------------------------------- 1 | // ====== --- ====== > Import Modules & Variables Declaration < ====== --- ====== // 2 | const mongoose = require("mongoose"); 3 | const bcrypt = require("bcrypt"); 4 | 5 | // ====== --- ====== > user schema < ====== --- ====== // 6 | /* 7 | //==// userSchema: it contains fields of user collection with some restrictions like 8 | (required, max, min) and some options like (default value, enum). 9 | user fields is [name, email, password, age, role, isDeleted]. 10 | */ 11 | const userSchema = mongoose.Schema( 12 | { 13 | name: { type: String, required: true }, 14 | email: { 15 | type: String, 16 | required: true, 17 | }, 18 | password: { type: String, required: true }, 19 | role: { type: String, default: "user" }, 20 | isDeleted: { type: Boolean, default: false }, 21 | }, 22 | 23 | { 24 | timestamps: true, // To save (creation, update) time 25 | } 26 | ); 27 | 28 | // ====== --- ====== > User Hooks < ====== --- ====== // 29 | userSchema.pre("save", async function () { 30 | this.password = await bcrypt.hash(this.password, 12); 31 | }); 32 | 33 | // ====== --- ====== > user model < ====== --- ====== // 34 | const users = mongoose.model("users", userSchema); // create user collection with given (name, schema). 35 | 36 | // ====== --- ====== > export user model < ====== --- ====== // 37 | module.exports = users; 38 | -------------------------------------------------------------------------------- /client/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import "./index.css"; 4 | import App from "./App"; 5 | import reportWebVitals from "./reportWebVitals"; 6 | import { Provider } from "react-redux"; 7 | import { store } from "./Redux/Store/store"; 8 | import { BrowserRouter } from "react-router-dom"; 9 | 10 | import "bootstrap/dist/css/bootstrap.min.css"; 11 | import "rc-pagination/assets/index.css"; 12 | 13 | // document.addEventListener("contextmenu", function (e) { 14 | // e.preventDefault(); 15 | // }); 16 | // document.onkeydown = function (e) { 17 | // if (e.keyCode === 123) { 18 | // return false; 19 | // } 20 | // if (e.ctrlKey && e.shiftKey && e.keyCode === "I".charCodeAt(0)) { 21 | // return false; 22 | // } 23 | // if (e.ctrlKey && e.shiftKey && e.keyCode === "C".charCodeAt(0)) { 24 | // return false; 25 | // } 26 | // if (e.ctrlKey && e.shiftKey && e.keyCode === "J".charCodeAt(0)) { 27 | // return false; 28 | // } 29 | // if (e.ctrlKey && e.keyCode === "U".charCodeAt(0)) { 30 | // return false; 31 | // } 32 | // }; 33 | 34 | const root = ReactDOM.createRoot(document.getElementById("root")); 35 | root.render( 36 | 37 | 38 | 39 | 40 | 41 | ); 42 | 43 | // If you want to start measuring performance in your app, pass a function 44 | // to log results (for example: reportWebVitals(console.log)) 45 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 46 | reportWebVitals(); 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Memories 2 | MERN stack social media app 3 | 4 | ## App features 5 | O Add Post 6 | O Edit Post 7 | O Delete Post 8 | O Like Post 9 | O Comment Post 10 | O Recommend Posts 11 | 12 | ## It`s built with the following technologies 13 | ### Front-end 14 | #### O react 15 | #### O axios 16 | #### O redux & redux-thunk 17 | #### O react-bootstrap 18 | #### O react-router-dom 19 | #### O jwt-decode 20 | #### O material-ui-chip-input 21 | #### O rc-pagination 22 | #### O react-file-base64 23 | #### O react-google-login 24 | #### O @fortawesome/react-fontawesome 25 | 26 | ### Back-end 27 | #### O node.js with express 28 | #### O mongodb 29 | #### O JWT 30 | #### O joi 31 | #### O rbac 32 | #### O body-parse 33 | #### O cors 34 | 35 | ![chrome_CBZbqTIqal](https://user-images.githubusercontent.com/76254195/185897771-f294c2f6-8491-4ed0-907a-c67253b5ff4e.png) 36 | 37 | ![chrome_grrJcLghvj](https://user-images.githubusercontent.com/76254195/185897785-adc6149d-fa78-4a70-8936-229c2174f7ed.png) 38 | 39 | ![chrome_KH99hDYnGM](https://user-images.githubusercontent.com/76254195/185897809-0afb25d0-a0ed-48cf-98bf-43fd2b767349.png) 40 | 41 | ![chrome_o2XFTG3EFC](https://user-images.githubusercontent.com/76254195/185897863-f5902946-696b-404b-8299-de9b80721933.png) 42 | 43 | ![chrome_wg1JfwVhQW](https://user-images.githubusercontent.com/76254195/185897907-e9c6b499-9ca5-4713-8651-b5c9a4bd191a.png) 44 | 45 | ![chrome_RBokNkbiCj](https://user-images.githubusercontent.com/76254195/185897920-b2ea2df9-e069-45e2-9f4e-ed3220df1b3d.png) 46 | 47 | ## App Link: https://memories-front-cmp.herokuapp.com/ 48 | ## Try & not forget the star 49 | -------------------------------------------------------------------------------- /server/Common/Middlewares/requestValidation.js: -------------------------------------------------------------------------------- 1 | /* ====================== /// <==> Variables Declaration <==> /// ====================== */ 2 | const { StatusCodes } = require("http-status-codes"); 3 | 4 | /* ================ /// <==> Validate request Fucntion <==> /// ================ */ 5 | /* 6 | //==// it's a middleware that mainly used to check the validity of sent request 7 | from user before stage of database operations. it takes just one parameter (request schema) and 8 | through function validate it compares between schema and sent request. 9 | */ 10 | const ValidateRequest = (schema) => { 11 | return (req, res, next) => { 12 | try { 13 | const validationErorrs = []; 14 | ["headers", "params", "query", "body"].forEach((key) => { 15 | if (schema[key]) { 16 | console.log(req[key]); 17 | let validation = schema[key].validate(req[key]); 18 | if (validation.error) { 19 | const valMesg = validation.error.details[0].message 20 | .split('"') 21 | .join(); 22 | validationErorrs.push(valMesg); 23 | } 24 | } 25 | }); 26 | 27 | if (validationErorrs.length) { 28 | console.log("Error in Sent req"); 29 | res.status(StatusCodes.BAD_REQUEST).json(validationErorrs[0]); 30 | } else next(); 31 | } catch (error) { 32 | console.log("Error In Validate req Function"); 33 | res 34 | .status(StatusCodes.INTERNAL_SERVER_ERROR) 35 | .json("Error In Validate req Function"); 36 | } 37 | }; 38 | }; 39 | 40 | /* ================ /// <==> Export validate request Function <==> /// ================ */ 41 | module.exports = ValidateRequest; 42 | -------------------------------------------------------------------------------- /server/modules/posts/model/post-model.js: -------------------------------------------------------------------------------- 1 | // ====== --- ====== > Import Modules & Variables Declaration < ====== --- ====== // 2 | const mongoose = require("mongoose"); 3 | const bcrypt = require("bcrypt"); 4 | 5 | // ====== --- ====== > Post schema < ====== --- ====== // 6 | /* 7 | //==// postSchema: it contains fields of post collection with some restrictions like 8 | (required, max, min) and some options like (default value, enum). 9 | post fields is [title, message, creator, tags, selectedFile, likeCount, createdAt, isDeleted]. 10 | */ 11 | const postSchema = mongoose.Schema( 12 | { 13 | title: { type: String, required: true }, 14 | message: { 15 | type: String, 16 | required: true, 17 | }, 18 | creator: { type: String, required: true }, // relate this field with user collection 19 | postName: { type: String, required: true }, 20 | tags: [{ type: String, required: true }], 21 | file: { type: String, required: true }, 22 | filePath: { type: String, required: true }, 23 | likeCount: [ 24 | { 25 | type: String, 26 | required: true, 27 | default: [], 28 | }, 29 | ], 30 | comments: [ 31 | { 32 | type: String, 33 | required: true, 34 | default: [], 35 | }, 36 | ], 37 | createdAt: { 38 | type: Date, 39 | default: new Date(), 40 | }, 41 | isDeleted: { type: Boolean, default: false }, 42 | }, 43 | 44 | { 45 | timestamps: true, // To save (creation, update) time 46 | } 47 | ); 48 | 49 | // ====== --- ====== > post model < ====== --- ====== // 50 | const posts = mongoose.model("posts", postSchema); // create post collection with given (name, schema). 51 | 52 | // ====== --- ====== > export post model < ====== --- ====== // 53 | module.exports = posts; 54 | -------------------------------------------------------------------------------- /server/modules/users/routes/user-routes.js: -------------------------------------------------------------------------------- 1 | // ====== --- ====== > Import Modules & Variables Declaration < ====== --- ====== // 2 | const express = require("express"); 3 | const router = express.Router(); 4 | const userFunctions = require("../controller/user-control"); 5 | const userSchemas = require("../joi/user-joi"); 6 | const validateRequest = require("../../../Common/Middlewares/requestValidation"); 7 | const userEndpoints = require("../user-endpoints"); 8 | const isAuthorized = require("../../../Common/Middlewares/isAuthorized"); 9 | /* 10 | //==// require express to create sub-object that will used to contains user apis 11 | //==// userFunctions: it's an object that contains all user apis logic 12 | //==// userSchemas: it's an object that contains all user apis schemas 13 | //==// validateRequest: it's a function that used validate schema with sent request 14 | */ 15 | 16 | // ====== --- ====== > User Routes < ====== --- ====== // 17 | 18 | // signup api 19 | router.get("/", (req, res) => { 20 | res.send("Hello, CMP!"); 21 | }); 22 | 23 | // signup api 24 | router.post( 25 | "/user/signup", 26 | validateRequest(userSchemas.signupSchema), 27 | userFunctions.signUp 28 | ); 29 | 30 | // signin api 31 | router.post( 32 | "/user/signin", 33 | validateRequest(userSchemas.signinSchema), 34 | userFunctions.signIn 35 | ); 36 | 37 | // signin api 38 | router.post( 39 | "/google", 40 | validateRequest(userSchemas.googleSigninSchema), 41 | userFunctions.googleSignIn 42 | ); 43 | 44 | // update api 45 | router.post( 46 | "/user-update-password", 47 | validateRequest(userSchemas.updatePasswordSchema), 48 | isAuthorized(userEndpoints.updateUserPassword), 49 | userFunctions.updatePassword 50 | ); 51 | // ====== --- ====== > Export Module < ====== --- ====== // 52 | module.exports = router; 53 | -------------------------------------------------------------------------------- /client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | Memories 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@emotion/react": "^11.10.0", 7 | "@emotion/styled": "^11.10.0", 8 | "@fortawesome/fontawesome-svg-core": "^6.1.2", 9 | "@fortawesome/free-regular-svg-icons": "^6.1.2", 10 | "@fortawesome/free-solid-svg-icons": "^6.1.2", 11 | "@fortawesome/react-fontawesome": "^0.2.0", 12 | "@material-ui/core": "4.0.0", 13 | "@mui/material": "^5.10.1", 14 | "@mui/styled-engine-sc": "^5.10.1", 15 | "@mui/styles": "^5.9.3", 16 | "@testing-library/jest-dom": "^5.16.5", 17 | "@testing-library/react": "^13.3.0", 18 | "@testing-library/user-event": "^13.5.0", 19 | "axios": "^0.27.2", 20 | "bootstrap": "^5.2.0", 21 | "gapi-script": "^1.2.0", 22 | "jwt-decode": "^3.1.2", 23 | "jwt-decoder": "^0.0.0", 24 | "material-ui-chip-input": "^2.0.0-beta.2", 25 | "moment": "^2.29.4", 26 | "rc-pagination": "^3.1.17", 27 | "react": "^18.2.0", 28 | "react-bootstrap": "^2.5.0", 29 | "react-dom": "^18.2.0", 30 | "react-file-base64": "^1.0.3", 31 | "react-google-login": "^5.2.2", 32 | "react-redux": "^8.0.2", 33 | "react-router-dom": "^6.3.0", 34 | "react-scripts": "5.0.1", 35 | "redux": "^4.2.0", 36 | "redux-thunk": "^2.4.1", 37 | "sass": "^1.54.4", 38 | "styled-components": "^5.3.5", 39 | "web-vitals": "^2.1.4" 40 | }, 41 | "scripts": { 42 | "start": "react-scripts start", 43 | "build": "react-scripts build", 44 | "test": "react-scripts test", 45 | "eject": "react-scripts eject" 46 | }, 47 | "eslintConfig": { 48 | "extends": [ 49 | "react-app", 50 | "react-app/jest" 51 | ] 52 | }, 53 | "browserslist": { 54 | "production": [ 55 | ">0.2%", 56 | "not dead", 57 | "not op_mini all" 58 | ], 59 | "development": [ 60 | "last 1 chrome version", 61 | "last 1 firefox version", 62 | "last 1 safari version" 63 | ] 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /server/modules/users/joi/user-joi.js: -------------------------------------------------------------------------------- 1 | /* ====================== /// <==> Variables Declaration <==> /// ====================== */ 2 | const joi = require("joi"); 3 | 4 | /* ================ /// <==> User Joi Validations <==> /// ================ */ 5 | /* 6 | //==//userJoi is an object that contains all user apis schemas to check the validity of sent request. 7 | this object attribures are [adduserSchema]. 8 | */ 9 | const userJoi = { 10 | signupSchema: { 11 | body: joi 12 | .object() 13 | .required() 14 | .keys({ 15 | name: joi.string().required(), 16 | email: joi 17 | .string() 18 | .email({ 19 | minDomainSegments: 2, 20 | tlds: { allow: ["com", "net"] }, 21 | }) 22 | .required(), 23 | password: joi.string().required(), 24 | confirmPassword: joi.string().required(), 25 | }), 26 | }, 27 | signinSchema: { 28 | body: joi 29 | .object() 30 | .required() 31 | .keys({ 32 | email: joi 33 | .string() 34 | .email({ 35 | minDomainSegments: 2, 36 | tlds: { allow: ["com", "net"] }, 37 | }) 38 | .required(), 39 | password: joi.string().required(), 40 | }), 41 | }, 42 | googleSigninSchema: { 43 | body: joi 44 | .object() 45 | .required() 46 | .keys({ 47 | email: joi 48 | .string() 49 | .email({ 50 | minDomainSegments: 2, 51 | tlds: { allow: ["com", "net"] }, 52 | }) 53 | .required(), 54 | name: joi.string().required(), 55 | }), 56 | }, 57 | updatePasswordSchema: { 58 | body: joi.object().required().keys({ 59 | oldPassword: joi.string().required(), 60 | newPassword: joi.string().required(), 61 | }), 62 | headers: joi 63 | .object() 64 | .required() 65 | .keys({ 66 | authorization: joi.string().required(), 67 | }) 68 | .options({ allowUnknown: true }), 69 | }, 70 | }; 71 | 72 | /* ============= /// <==> Exports User Joi Validations <==> /// ============= */ 73 | module.exports = userJoi; 74 | -------------------------------------------------------------------------------- /server/Common/Middlewares/isAuthorized.js: -------------------------------------------------------------------------------- 1 | // ====== --- ====== > Variables Declarations < ====== --- ====== // 2 | const { StatusCodes } = require("http-status-codes"); 3 | const jwt = require("jsonwebtoken"); 4 | const userRbac = require("../Rbac/rbac"); 5 | 6 | // ====== --- ====== > Authorization Function < ====== --- ====== // 7 | /* 8 | //==// it's a middleware that mainly used to check the Authority of sent req 9 | from user before stage of database operations. it takes just one parameter (req endpoint). 10 | */ 11 | 12 | const isAuthorized = (endPoint) => { 13 | return async (req, res, next) => { 14 | try { 15 | if (req.headers.authorization) { 16 | const token = req.headers.authorization.split(" ")[1]; 17 | 18 | if (token) { 19 | if (token.length < 500) { 20 | // Memories Token 21 | var decoded = jwt.verify(token, process.env.ENCRYPT_KEY).data; 22 | var isAllowed = await userRbac.can( 23 | decoded.role.toString(), 24 | endPoint 25 | ); 26 | } else { 27 | // Google Token Token 28 | var decoded = jwt.decode(token); 29 | console.log({ decoded }); 30 | var isAllowed = await userRbac.can("user", endPoint); 31 | } 32 | 33 | if (isAllowed) { 34 | req.decoded = decoded; 35 | next(); 36 | } else { 37 | res.status(StatusCodes.UNAUTHORIZED).json({ 38 | Message: "UNAUTHORIZED", 39 | }); 40 | } 41 | } else { 42 | res.status(StatusCodes.UNAUTHORIZED).json({ 43 | Message: "Invalid Token", 44 | }); 45 | } 46 | } else { 47 | res.status(StatusCodes.BAD_req).json({ 48 | Message: "Token is required", 49 | }); 50 | } 51 | } catch (error) { 52 | res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ 53 | Message: "Error In Is Autorized Function", 54 | }); 55 | console.log(error); 56 | } 57 | }; 58 | }; 59 | 60 | // ====== --- ====== > Exports Authorization Function < ====== --- ====== // 61 | module.exports = isAuthorized; 62 | -------------------------------------------------------------------------------- /client/src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/App.js: -------------------------------------------------------------------------------- 1 | import Style from "./App.scss"; 2 | import Home from "./Components/Home/Home"; 3 | import { Routes, Route, Navigate } from "react-router-dom"; 4 | import Authentication from "./Components/Authentication/Authentication"; 5 | import Navigationbar from "./Components/Navigationbar/Navigationbar"; 6 | import ProtectedRoute from "./Components/ProtectedRoute/ProtectedRoute"; 7 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 8 | import { faArrowCircleUp } from "@fortawesome/free-solid-svg-icons"; 9 | import React, { useState } from "react"; 10 | import { useSelector } from "react-redux"; 11 | import PostDetails from "./Components/PostDetails/PostDetails"; 12 | 13 | function App() { 14 | const [visible, setVisible] = useState(false); 15 | const googleAuth = useSelector((state) => state.googleAuth); 16 | const toggleVisible = () => { 17 | const scrolled = document.documentElement.scrollTop; 18 | if (scrolled > 300) { 19 | setVisible(true); 20 | } else if (scrolled <= 300) { 21 | setVisible(false); 22 | } 23 | }; 24 | 25 | const scrollToTop = () => { 26 | window.scrollTo({ 27 | top: 0, 28 | behavior: "smooth", 29 | /* you can also use 'auto' behaviour 30 | in place of 'smooth' */ 31 | }); 32 | }; 33 | 34 | window.addEventListener("scroll", toggleVisible); 35 | 36 | return ( 37 | <> 38 |
39 | 40 | 41 | }> 42 | } /> 43 | } 47 | /> 48 | } /> 49 | } /> 50 | 51 | 52 | } /> 53 | } 57 | /> 58 | } /> 59 | 60 | 61 | 74 |
75 | 76 | ); 77 | } 78 | 79 | export default App; 80 | -------------------------------------------------------------------------------- /server/modules/posts/routes/post-routes.js: -------------------------------------------------------------------------------- 1 | // ====== --- ====== > Import Modules & Variables Declaration < ====== --- ====== // 2 | const express = require("express"); 3 | const router = express.Router(); 4 | const postFunctions = require("../controller/post-control"); 5 | const postSchemas = require("../joi/post-joi"); 6 | const validateRequest = require("../../../Common/Middlewares/requestValidation"); 7 | const postEndpoints = require("../post-endpoints"); 8 | const isAuthorized = require("../../../Common/Middlewares/isAuthorized"); 9 | /* 10 | //==// require express to create sub-object that will used to contains post apis 11 | //==// postFunctions: it's an object that contains all post apis logic 12 | //==// postSchemas: it's an object that contains all post apis schemas 13 | //==// validateRequest: it's a function that used validate schema with sent request 14 | //==// postEndpoints: it's an object that contains all post api endpoints 15 | //==// isAuthorized: it's a funtion that checks the authority of the user 16 | */ 17 | 18 | // ====== --- ====== > Post Routes < ====== --- ====== // 19 | 20 | // get posts api 21 | router.get( 22 | "/posts", 23 | validateRequest(postSchemas.getPostsSchema), 24 | postFunctions.getPosts 25 | ); 26 | 27 | // add post api 28 | router.post( 29 | "/post/add", 30 | validateRequest(postSchemas.addPostSchema), 31 | isAuthorized(postEndpoints.AddPost), 32 | postFunctions.addPost 33 | ); 34 | 35 | // edit post api 36 | router.put( 37 | "/post/edit/:id", 38 | validateRequest(postSchemas.editPostSchema), 39 | isAuthorized(postEndpoints.editPost), 40 | postFunctions.editPost 41 | ); 42 | 43 | // delete post api 44 | router.delete( 45 | "/post/delete/:id", 46 | validateRequest(postSchemas.deletePostSchema), 47 | isAuthorized(postEndpoints.deletePost), 48 | postFunctions.deletePost 49 | ); 50 | 51 | // like post api 52 | router.patch( 53 | "/post/like/:id", 54 | validateRequest(postSchemas.likePostSchema), 55 | isAuthorized(postEndpoints.likePost), 56 | postFunctions.likePost 57 | ); 58 | 59 | // search post api 60 | router.get( 61 | "/post/search/", 62 | validateRequest(postSchemas.searchPostSchema), 63 | isAuthorized(postEndpoints.searchPost), 64 | postFunctions.searchPost 65 | ); 66 | 67 | // get post by id api 68 | router.get( 69 | "/post/detail/:id", 70 | validateRequest(postSchemas.getPostSchema), 71 | isAuthorized(postEndpoints.getPost), 72 | postFunctions.getPost 73 | ); 74 | 75 | // add comment api 76 | router.post( 77 | "/post/comment/:id", 78 | validateRequest(postSchemas.addCommentSchema), 79 | isAuthorized(postEndpoints.addComment), 80 | postFunctions.addComment 81 | ); 82 | 83 | // ====== --- ====== > Export Module < ====== --- ====== // 84 | module.exports = router; 85 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | // ====== --- ====== > Import Modules & Variables Declaration < ====== --- ====== // 2 | const express = require("express"); 3 | const app = express(); 4 | const env = require("dotenv"); 5 | const cors = require("cors"); 6 | var bodyParser = require("body-parser"); 7 | 8 | /* 9 | //==// Express is a minimal and flexible Node.js web application framework that 10 | provides a robust set of features for web and mobile applications. 11 | 12 | //==// Dotenv is a zero-dependency module that loads environment variables from a 13 | .env file into process.env. Storing configuration in the environment separate from 14 | code is based on The Twelve-Factor App methodology. 15 | 16 | //==// (CORS) is an HTTP-header based mechanism that allows a server to indicate any 17 | origins (domain, scheme, or port) other than its own from which a browser should permit loading resources. 18 | 19 | //==// bodyParser used to deal with post requests 20 | */ 21 | const userRouter = require("./modules/users/routes/user-routes"); 22 | const postRouter = require("./modules/posts/routes/post-routes"); 23 | const Connection = require("./Configration/configDB"); 24 | /* 25 | //==// userRouter: is a router object that contains user module apis. 26 | //==// postRouter: is a router object that contains post module apis. 27 | //==// Connection: function that used to connection with mongodb 28 | */ 29 | 30 | // ====== --- ====== > calling some functions < ====== --- ====== // 31 | env.config(); 32 | Connection(); 33 | /* 34 | //==// the config method takes a .env file path as an argument, 35 | it parses it and sets environment vars defined in that file in process.env. 36 | 37 | //==// call connection function of mongodb. 38 | */ 39 | 40 | // ====== --- ====== > Server APIs < ====== --- ====== // 41 | app.use(cors()); 42 | app.use(bodyParser.json({ limit: "50mb" })); 43 | app.use(bodyParser.urlencoded({ limit: "50mb", extended: true })); 44 | app.use(express.json()); // General Middelware 45 | app.use(userRouter); // user routes 46 | app.use(postRouter); // post routes 47 | /* 48 | //==// To setup your middleware, you can invoke app.use() for every middleware 49 | layer that you want to add (it can be generic to all paths, or triggered only on specific path(s) 50 | your server handles), and it will add onto your Express middleware stack. 51 | */ 52 | 53 | // ====== --- ====== > Listen Server On Port < ====== --- ====== // 54 | app.listen(process.env.PORT, () => { 55 | console.log(`App listening on port ${process.env.PORT} !`); 56 | }); 57 | /* 58 | //==//The app.listen() function is used to bind and listen the connections on the specified host and port. 59 | This method is identical to Node’s http.Server.listen() method. If the port number is omitted or is 0, 60 | the operating system will assign an arbitrary unused port, which is useful for cases like automated tasks (tests, etc.). 61 | */ 62 | -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in your browser. 13 | 14 | The page will reload when you make changes.\ 15 | You may also see any lint errors in the console. 16 | 17 | ### `npm test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `npm run build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `npm run eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can't go back!** 35 | 36 | If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 37 | 38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own. 39 | 40 | You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | 48 | ### Code Splitting 49 | 50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) 51 | 52 | ### Analyzing the Bundle Size 53 | 54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) 55 | 56 | ### Making a Progressive Web App 57 | 58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) 59 | 60 | ### Advanced Configuration 61 | 62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) 63 | 64 | ### Deployment 65 | 66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) 67 | 68 | ### `npm run build` fails to minify 69 | 70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) 71 | -------------------------------------------------------------------------------- /server/modules/posts/joi/post-joi.js: -------------------------------------------------------------------------------- 1 | /* ====================== /// <==> Variables Declaration <==> /// ====================== */ 2 | const joi = require("joi"); 3 | 4 | /* ================ /// <==> Post Joi Validations <==> /// ================ */ 5 | /* 6 | //==//postJoi is an object that contains all post apis schemas to check the validity of sent request. 7 | this object attribures are [addPostSchema]. 8 | */ 9 | const userJoi = { 10 | addPostSchema: { 11 | body: joi 12 | .object() 13 | .required() 14 | .keys({ 15 | postName: joi.string().required(), 16 | title: joi.string().required(), 17 | message: joi.string().required(), 18 | tags: joi.array().items(joi.string()).required(), 19 | file: joi.string().required(), 20 | filePath: joi.string().required(), 21 | }), 22 | headers: joi 23 | .object() 24 | .required() 25 | .keys({ 26 | authorization: joi.string().required(), 27 | }) 28 | .options({ allowUnknown: true }), 29 | }, 30 | editPostSchema: { 31 | body: joi 32 | .object() 33 | .required() 34 | .keys({ 35 | userId: joi.string().required(), 36 | postName: joi.string().required(), 37 | title: joi.string().required(), 38 | message: joi.string().required(), 39 | tags: joi.array().items(joi.string()).required(), 40 | }), 41 | params: joi.object().required().keys({ 42 | id: joi.string().required(), 43 | }), 44 | headers: joi 45 | .object() 46 | .required() 47 | .keys({ 48 | authorization: joi.string().required(), 49 | }) 50 | .options({ allowUnknown: true }), 51 | }, 52 | deletePostSchema: { 53 | body: joi.object().required().keys({ 54 | userId: joi.string().required(), 55 | }), 56 | params: joi.object().required().keys({ 57 | id: joi.string().required(), 58 | }), 59 | headers: joi 60 | .object() 61 | .required() 62 | .keys({ 63 | authorization: joi.string().required(), 64 | }) 65 | .options({ allowUnknown: true }), 66 | }, 67 | likePostSchema: { 68 | params: joi.object().required().keys({ 69 | id: joi.string().required(), 70 | }), 71 | headers: joi 72 | .object() 73 | .required() 74 | .keys({ 75 | authorization: joi.string().required(), 76 | }) 77 | .options({ allowUnknown: true }), 78 | }, 79 | searchPostSchema: { 80 | query: joi.object().required().keys({ 81 | titles: joi.string().required(), 82 | tags: joi.string().required(), 83 | page: joi.string().required(), 84 | }), 85 | headers: joi 86 | .object() 87 | .required() 88 | .keys({ 89 | authorization: joi.string().required(), 90 | }) 91 | .options({ allowUnknown: true }), 92 | }, 93 | 94 | getPostsSchema: { 95 | query: joi.object().required().keys({ 96 | page: joi.string().required(), 97 | }), 98 | }, 99 | 100 | getPostSchema: { 101 | params: joi.object().required().keys({ 102 | id: joi.string().required(), 103 | }), 104 | headers: joi 105 | .object() 106 | .required() 107 | .keys({ 108 | authorization: joi.string().required(), 109 | }) 110 | .options({ allowUnknown: true }), 111 | }, 112 | 113 | addCommentSchema: { 114 | params: joi.object().required().keys({ 115 | id: joi.string().required(), 116 | }), 117 | body: joi.object().required().keys({ 118 | comment: joi.string().required(), 119 | }), 120 | headers: joi 121 | .object() 122 | .required() 123 | .keys({ 124 | authorization: joi.string().required(), 125 | }) 126 | .options({ allowUnknown: true }), 127 | }, 128 | }; 129 | 130 | /* ============= /// <==> Exports User Joi Validations <==> /// ============= */ 131 | module.exports = userJoi; 132 | -------------------------------------------------------------------------------- /client/src/index.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Montserrat+Alternates:wght@300&display=swap"); 2 | 3 | body { 4 | font-family: "Montserrat Alternates", sans-serif !important; 5 | background-color: #11aa44; 6 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='400' height='400' viewBox='0 0 200 200'%3E%3Cg fill='none' stroke='%237F3' stroke-width='1' stroke-opacity='0.2'%3E%3Crect x='-40' y='40' width='75' height='75'/%3E%3Crect x='-35' y='45' width='65' height='65'/%3E%3Crect x='-30' y='50' width='55' height='55'/%3E%3Crect x='-25' y='55' width='45' height='45'/%3E%3Crect x='-20' y='60' width='35' height='35'/%3E%3Crect x='-15' y='65' width='25' height='25'/%3E%3Crect x='-10' y='70' width='15' height='15'/%3E%3Crect x='-5' y='75' width='5' height='5'/%3E%3Crect width='35' height='35'/%3E%3Crect x='5' y='5' width='25' height='25'/%3E%3Crect x='10' y='10' width='15' height='15'/%3E%3Crect x='15' y='15' width='5' height='5'/%3E%3Crect x='40' width='75' height='75'/%3E%3Crect x='45' y='5' width='65' height='65'/%3E%3Crect x='50' y='10' width='55' height='55'/%3E%3Crect x='55' y='15' width='45' height='45'/%3E%3Crect x='60' y='20' width='35' height='35'/%3E%3Crect x='65' y='25' width='25' height='25'/%3E%3Crect x='70' y='30' width='15' height='15'/%3E%3Crect x='75' y='35' width='5' height='5'/%3E%3Crect x='40' y='80' width='35' height='35'/%3E%3Crect x='45' y='85' width='25' height='25'/%3E%3Crect x='50' y='90' width='15' height='15'/%3E%3Crect x='55' y='95' width='5' height='5'/%3E%3Crect x='120' y='-40' width='75' height='75'/%3E%3Crect x='125' y='-35' width='65' height='65'/%3E%3Crect x='130' y='-30' width='55' height='55'/%3E%3Crect x='135' y='-25' width='45' height='45'/%3E%3Crect x='140' y='-20' width='35' height='35'/%3E%3Crect x='145' y='-15' width='25' height='25'/%3E%3Crect x='150' y='-10' width='15' height='15'/%3E%3Crect x='155' y='-5' width='5' height='5'/%3E%3Crect x='120' y='40' width='35' height='35'/%3E%3Crect x='125' y='45' width='25' height='25'/%3E%3Crect x='130' y='50' width='15' height='15'/%3E%3Crect x='135' y='55' width='5' height='5'/%3E%3Crect y='120' width='75' height='75'/%3E%3Crect x='5' y='125' width='65' height='65'/%3E%3Crect x='10' y='130' width='55' height='55'/%3E%3Crect x='15' y='135' width='45' height='45'/%3E%3Crect x='20' y='140' width='35' height='35'/%3E%3Crect x='25' y='145' width='25' height='25'/%3E%3Crect x='30' y='150' width='15' height='15'/%3E%3Crect x='35' y='155' width='5' height='5'/%3E%3Crect x='200' y='120' width='75' height='75'/%3E%3Crect x='40' y='200' width='75' height='75'/%3E%3Crect x='80' y='80' width='75' height='75'/%3E%3Crect x='85' y='85' width='65' height='65'/%3E%3Crect x='90' y='90' width='55' height='55'/%3E%3Crect x='95' y='95' width='45' height='45'/%3E%3Crect x='100' y='100' width='35' height='35'/%3E%3Crect x='105' y='105' width='25' height='25'/%3E%3Crect x='110' y='110' width='15' height='15'/%3E%3Crect x='115' y='115' width='5' height='5'/%3E%3Crect x='80' y='160' width='35' height='35'/%3E%3Crect x='85' y='165' width='25' height='25'/%3E%3Crect x='90' y='170' width='15' height='15'/%3E%3Crect x='95' y='175' width='5' height='5'/%3E%3Crect x='120' y='160' width='75' height='75'/%3E%3Crect x='125' y='165' width='65' height='65'/%3E%3Crect x='130' y='170' width='55' height='55'/%3E%3Crect x='135' y='175' width='45' height='45'/%3E%3Crect x='140' y='180' width='35' height='35'/%3E%3Crect x='145' y='185' width='25' height='25'/%3E%3Crect x='150' y='190' width='15' height='15'/%3E%3Crect x='155' y='195' width='5' height='5'/%3E%3Crect x='160' y='40' width='75' height='75'/%3E%3Crect x='165' y='45' width='65' height='65'/%3E%3Crect x='170' y='50' width='55' height='55'/%3E%3Crect x='175' y='55' width='45' height='45'/%3E%3Crect x='180' y='60' width='35' height='35'/%3E%3Crect x='185' y='65' width='25' height='25'/%3E%3Crect x='190' y='70' width='15' height='15'/%3E%3Crect x='195' y='75' width='5' height='5'/%3E%3Crect x='160' y='120' width='35' height='35'/%3E%3Crect x='165' y='125' width='25' height='25'/%3E%3Crect x='170' y='130' width='15' height='15'/%3E%3Crect x='175' y='135' width='5' height='5'/%3E%3Crect x='200' y='200' width='35' height='35'/%3E%3Crect x='200' width='35' height='35'/%3E%3Crect y='200' width='35' height='35'/%3E%3C/g%3E%3C/svg%3E"); 7 | } 8 | -------------------------------------------------------------------------------- /client/src/Components/Navigationbar/Navigationbar.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import Style from "../Home/Home.module.scss"; 3 | import Container from "react-bootstrap/Container"; 4 | import Nav from "react-bootstrap/Nav"; 5 | import Navbar from "react-bootstrap/Navbar"; 6 | import { NavLink } from "react-router-dom"; 7 | import jwt_decode from "jwt-decode"; 8 | import { useDispatch, useSelector } from "react-redux"; 9 | import { 10 | getPostsAction, 11 | logOutAction, 12 | serachPostAction, 13 | updateGoogleAuthAction, 14 | } from "../../Redux/Actions/actions"; 15 | import { useLocation } from "react-router-dom"; 16 | import { useNavigate } from "react-router-dom"; 17 | 18 | function Navigationbar() { 19 | const dispatch = useDispatch(); 20 | const memoryProfile = JSON.parse(localStorage.getItem("memoryProfile")); 21 | const googleAuth = useSelector((state) => state.googleAuth); 22 | const [user, setUser] = useState(null); 23 | let navigate = useNavigate(); 24 | let location = useLocation(); 25 | 26 | useEffect(() => { 27 | const fun = async () => { 28 | try { 29 | if (location.pathname !== "/auth") { 30 | var decoded = jwt_decode(memoryProfile?.token); 31 | let exp = new Date().getTime(); 32 | if (decoded.exp * 1000 > exp) { 33 | if (Object.keys(googleAuth).length === 0) { 34 | await dispatch(updateGoogleAuthAction(memoryProfile)); 35 | } 36 | setUser(memoryProfile?.profile || decoded.data); 37 | } else { 38 | setUser(null); 39 | localStorage.clear(); 40 | navigate("/auth", { replace: true }); 41 | } 42 | } else { 43 | setUser(null); 44 | } 45 | } catch (error) { 46 | setUser(null); 47 | localStorage.clear(); 48 | navigate("/auth", { replace: true }); 49 | console.log(error); 50 | } 51 | }; 52 | 53 | fun(); 54 | }, [location, dispatch]); 55 | 56 | return ( 57 | <> 58 | 63 | 64 | 69 |

{ 71 | await dispatch(getPostsAction(1)); 72 | navigate(`/posts?page=${1}`, { 73 | replace: true, 74 | }); 75 | }} 76 | className={["title text-info"].join(" ")} 77 | > 78 | Memories 79 |

80 |
81 | 82 | 83 | 123 | 124 |
125 |
126 | 127 | ); 128 | } 129 | 130 | export default Navigationbar; 131 | -------------------------------------------------------------------------------- /client/src/APIs/APIs.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import jwt_decode from "jwt-decode"; 3 | const baseURL = "https://memories-cmp.herokuapp.com/"; 4 | 5 | export const addPostAPI = async (post, googleAuth) => { 6 | try { 7 | post.tags = post.tags.filter((element) => { 8 | if (element.length !== 0) { 9 | return true; 10 | } 11 | return false; 12 | }); 13 | const res = await axios.post(`${baseURL}post/add`, post, { 14 | headers: { 15 | authorization: `Bearer ${googleAuth.token}`, 16 | }, 17 | }); 18 | return res; 19 | } catch (error) { 20 | console.log(error); 21 | } 22 | }; 23 | 24 | export const getAllPostsAPI = async (page) => { 25 | try { 26 | const res = await axios.get(`${baseURL}posts?page=${page}`); 27 | return res; 28 | } catch (error) { 29 | console.log(error); 30 | } 31 | }; 32 | 33 | export const getPostAPI = async (id, googleAuth) => { 34 | try { 35 | const res = await axios.get(`${baseURL}post/detail/${id}`, { 36 | headers: { 37 | authorization: `Bearer ${googleAuth.token}`, 38 | }, 39 | }); 40 | return res; 41 | } catch (error) { 42 | console.log(error); 43 | } 44 | }; 45 | 46 | export const editPostAPI = async (post, googleAuth) => { 47 | try { 48 | const res = await axios.put( 49 | `${baseURL}post/edit/${post._id}`, 50 | { 51 | userId: post.creator, 52 | postName: post.postName, 53 | title: post.title, 54 | message: post.message, 55 | tags: post.tags.filter((element) => { 56 | if (element.length !== 0) { 57 | return true; 58 | } 59 | return false; 60 | }), 61 | }, 62 | { 63 | headers: { 64 | authorization: `Bearer ${googleAuth.token}`, 65 | }, 66 | } 67 | ); 68 | return res; 69 | } catch (error) { 70 | console.log(error); 71 | } 72 | }; 73 | 74 | export const deletePostAPI = async (post, googleAuth) => { 75 | try { 76 | const res = await axios.delete(`${baseURL}post/delete/${post._id}`, { 77 | headers: { 78 | authorization: `Bearer ${googleAuth.token}`, 79 | }, 80 | data: { 81 | userId: post.creator, 82 | }, 83 | }); 84 | return res; 85 | } catch (error) { 86 | console.log(error); 87 | } 88 | }; 89 | 90 | export const likePostAPI = async (post, googleAuth) => { 91 | try { 92 | const res = await axios.patch( 93 | `${baseURL}post/like/${post._id}`, 94 | {}, 95 | { 96 | headers: { 97 | authorization: `Bearer ${googleAuth.token}`, 98 | }, 99 | } 100 | ); 101 | return res; 102 | } catch (error) { 103 | console.log(error); 104 | } 105 | }; 106 | 107 | export const searchPostAPI = async (titles, tags, googleAuth, page) => { 108 | try { 109 | const res = await axios.get( 110 | `${baseURL}post/search?titles=${titles}&tags=${tags}&page=${page}`, 111 | { 112 | headers: { 113 | authorization: `Bearer ${googleAuth.token}`, 114 | }, 115 | } 116 | ); 117 | return res; 118 | } catch (error) { 119 | console.log(error); 120 | } 121 | }; 122 | 123 | export const signUpAPI = async (user) => { 124 | try { 125 | const res = await axios.post(`${baseURL}user/signup`, user); 126 | return res; 127 | } catch (error) { 128 | console.log(error); 129 | } 130 | }; 131 | 132 | export const signInAPI = async (user) => { 133 | try { 134 | const res = await axios.post(`${baseURL}user/signin`, { 135 | email: user.email, 136 | password: user.password, 137 | }); 138 | return res; 139 | } catch (error) { 140 | console.log(error); 141 | } 142 | }; 143 | 144 | export const googleSigninAPI = async (token) => { 145 | try { 146 | var decoded = jwt_decode(token); 147 | const res = await axios.post(`${baseURL}google`, { 148 | email: decoded.email, 149 | name: decoded.name, 150 | }); 151 | return res; 152 | } catch (error) { 153 | console.log(error); 154 | } 155 | }; 156 | 157 | export const addCommentAPI = async (comment, id, googleAuth) => { 158 | try { 159 | const res = await axios.post( 160 | `${baseURL}post/comment/${id}`, 161 | { comment }, 162 | { 163 | headers: { 164 | authorization: `Bearer ${googleAuth.token}`, 165 | }, 166 | } 167 | ); 168 | return res; 169 | } catch (error) { 170 | console.log(error); 171 | } 172 | }; 173 | -------------------------------------------------------------------------------- /client/src/Components/Posts/Posts.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { useSelector, useDispatch } from "react-redux"; 3 | import { useLocation, useNavigate } from "react-router-dom"; 4 | import { getPostsAction, serachPostAction } from "../../Redux/Actions/actions"; 5 | import Post from "./Post/Post"; 6 | import Pagination from "rc-pagination"; 7 | 8 | function Posts() { 9 | const posts = useSelector((state) => state.posts); 10 | const dispatch = useDispatch(); 11 | const [waiting, setWaiting] = useState(true); 12 | let navigate = useNavigate(); 13 | let location = useLocation(); 14 | const memoryProfile = JSON.parse(localStorage.getItem("memoryProfile")); 15 | const postCount = useSelector((state) => state.postCount); 16 | const googleAuth = useSelector((state) => state.googleAuth); 17 | 18 | let [currentPage, setCurrentPage] = useState(1); 19 | let [parameters, setParameters] = useState({ titles: [], tags: [] }); 20 | 21 | const fetchMyAPI = async (page = 1) => { 22 | try { 23 | setWaiting(true); 24 | if (location.pathname === "/posts") { 25 | const params = new Proxy(new URLSearchParams(window.location.search), { 26 | get: (searchParams, prop) => searchParams.get(prop), 27 | }); 28 | let page = params.page; 29 | if (page) { 30 | setCurrentPage(Number(page)); 31 | } 32 | setWaiting(true); 33 | await dispatch(getPostsAction(page)); 34 | } else if (location.pathname === "/posts/search") { 35 | // If search post, get search post data 36 | const params = new Proxy(new URLSearchParams(window.location.search), { 37 | get: (searchParams, prop) => searchParams.get(prop), 38 | }); 39 | let titles = params.titles.split(","); 40 | let tags = params.tags.split(","); 41 | setParameters({ titles, tags }); 42 | let page = params.page; 43 | if (page) { 44 | setCurrentPage(Number(page)); 45 | } 46 | setWaiting(true); 47 | await dispatch(serachPostAction(titles, tags, memoryProfile, page)); 48 | } 49 | setWaiting(false); 50 | } catch (error) { 51 | localStorage.clear(); 52 | navigate("/auth", { replace: true }); 53 | } 54 | }; 55 | 56 | useEffect(() => { 57 | fetchMyAPI(); 58 | }, []); 59 | 60 | useEffect(() => { 61 | const params = new Proxy(new URLSearchParams(window.location.search), { 62 | get: (searchParams, prop) => searchParams.get(prop), 63 | }); 64 | let page = params.page; 65 | if (page) setCurrentPage(Number(page)); 66 | else setCurrentPage(Number(1)); 67 | }, [location]); 68 | 69 | return ( 70 | <> 71 | {waiting &&

Wait ...

} 72 | {!waiting && ( 73 |
74 | 75 |
76 | {postCount > 10 && ( 77 | { 83 | const params = new Proxy( 84 | new URLSearchParams(window.location.search), 85 | { 86 | get: (searchParams, prop) => searchParams.get(prop), 87 | } 88 | ); 89 | setCurrentPage(current); 90 | setWaiting(true); 91 | window.scrollTo({ 92 | top: 0, 93 | behavior: "smooth", 94 | }); 95 | // ============ Handle two cases ============= // 96 | if (location.pathname === "/posts") { 97 | await dispatch(getPostsAction(current)); 98 | navigate(`/posts?page=${current}`, { 99 | replace: true, 100 | }); 101 | } else if (location.pathname === "/posts/search") { 102 | await dispatch( 103 | serachPostAction( 104 | params.titles, 105 | params.tags, 106 | googleAuth, 107 | current 108 | ) 109 | ); 110 | navigate( 111 | `/posts/search?titles=${params.titles}&tags=${params.tags}&page=${current}`, 112 | { 113 | replace: true, 114 | } 115 | ); 116 | } 117 | setWaiting(false); 118 | }} 119 | /> 120 | )} 121 |
122 |
123 | )} 124 | 125 | ); 126 | } 127 | 128 | export default Posts; 129 | -------------------------------------------------------------------------------- /client/src/Components/SearchPost/SearchPost.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import Button from "react-bootstrap/Button"; 3 | import Form from "react-bootstrap/Form"; 4 | import { useDispatch, useSelector } from "react-redux"; 5 | import Alert from "react-bootstrap/Alert"; 6 | import { 7 | errorResetAction, 8 | serachPostAction, 9 | unexpectedErrorAction, 10 | } from "../../Redux/Actions/actions"; 11 | import { ERROR_SEARCH_POST } from "../../Redux/Actions/actionStrings"; 12 | import ChipInput from "material-ui-chip-input"; 13 | import { useNavigate } from "react-router-dom"; 14 | function SearchPost() { 15 | let navigate = useNavigate(); 16 | const dispatch = useDispatch(); 17 | const googleAuth = useSelector((state) => state.googleAuth); 18 | const error = useSelector((state) => state.error); 19 | const [searchPost, setSearchPost] = useState({ 20 | titles: [], 21 | tags: [], 22 | }); 23 | const [waiting, setWaiting] = useState(false); 24 | 25 | const saveSearchPost = (name, chips) => { 26 | setSearchPost({ ...searchPost, [name]: chips }); 27 | }; 28 | 29 | const deleteChip = (name, index) => { 30 | let arr = searchPost[name]; 31 | arr.splice(index, 1); 32 | setSearchPost({ ...searchPost, [name]: arr }); 33 | }; 34 | 35 | const clearForm = (e) => { 36 | e.preventDefault(); 37 | setSearchPost({ 38 | titles: [], 39 | tags: [], 40 | }); 41 | 42 | document.getElementById("searchPost").reset(); 43 | }; 44 | 45 | const submitSearch = async (e) => { 46 | e.preventDefault(); 47 | setWaiting(true); 48 | let result = Object.values(searchPost).every((p) => { 49 | return p.length; 50 | }); 51 | 52 | if (result) { 53 | const titles = searchPost.titles.map((value, index) => { 54 | return value.trim(); 55 | }); 56 | const tags = searchPost.tags.map((value, index) => { 57 | return value.trim(); 58 | }); 59 | await dispatch(errorResetAction()); 60 | await dispatch(serachPostAction(titles, tags, googleAuth)); 61 | navigate( 62 | `/posts/search?titles=${searchPost.titles}&tags=${ 63 | searchPost.tags 64 | }&page=${1}`, 65 | { 66 | replace: true, 67 | } 68 | ); 69 | clearForm(e); 70 | } else await dispatch(unexpectedErrorAction(ERROR_SEARCH_POST)); 71 | 72 | setWaiting(false); 73 | }; 74 | 75 | return ( 76 | <> 77 |
78 |

Search memories

79 |
80 | {/* 81 | 89 | 90 | 91 | 92 | 100 | */} 101 | 102 |
103 | saveSearchPost("titles", chips)} 107 | value={searchPost.titles} 108 | onDelete={(value, index) => deleteChip("titles", index)} 109 | /> 110 |
111 |
112 | saveSearchPost("tags", chips)} 116 | value={searchPost.tags} 117 | onDelete={(value, index) => deleteChip("tags", index)} 118 | /> 119 |
120 | 121 | {error.value && error.type === "search" && ( 122 | 123 | {error.message} 124 | 125 | )} 126 | 137 | 145 |
146 |
147 | 148 | ); 149 | } 150 | 151 | export default SearchPost; 152 | -------------------------------------------------------------------------------- /client/src/Redux/Actions/actions.jsx: -------------------------------------------------------------------------------- 1 | import { useNavigate } from "react-router-dom"; 2 | 3 | import { 4 | deletePostAPI, 5 | editPostAPI, 6 | getAllPostsAPI, 7 | likePostAPI, 8 | addPostAPI, 9 | signInAPI, 10 | signUpAPI, 11 | googleSigninAPI, 12 | searchPostAPI, 13 | getPostAPI, 14 | addCommentAPI, 15 | } from "../../APIs/APIs"; 16 | import { 17 | ADD_POST, 18 | DECREASE_POST_COUNT, 19 | DELETE_POST, 20 | EDIT_POST, 21 | EDIT_POST_ID, 22 | ERROR_RESET, 23 | GET_POSTS, 24 | GOOGLE_AUTH, 25 | INCREASE_POST_COUNT, 26 | LIKE_POST, 27 | LOGOUT, 28 | RESET_ID, 29 | SEARCH_POST, 30 | SET_POST_COUNT, 31 | SET_POST_DETAILS, 32 | SIGNIN, 33 | SIGNUP, 34 | UPDATE_GOOGLE_AUTH, 35 | } from "./actionStrings"; 36 | 37 | export const addPostAction = (post, googleAuth) => async (dispatch) => { 38 | const res = await addPostAPI(post, googleAuth); 39 | dispatch({ 40 | type: ADD_POST, 41 | payload: res.data.post, 42 | }); 43 | dispatch({ 44 | type: INCREASE_POST_COUNT, 45 | }); 46 | }; 47 | 48 | export const getPostsAction = 49 | (page = 1) => 50 | async (dispatch) => { 51 | const res = await getAllPostsAPI(page); 52 | dispatch({ 53 | type: GET_POSTS, 54 | payload: res.data.posts, 55 | }); 56 | dispatch({ 57 | type: SET_POST_COUNT, 58 | payload: res.data.totalCount, 59 | }); 60 | }; 61 | 62 | export const getPostAction = (id, googleAuth) => async (dispatch) => { 63 | const res = await getPostAPI(id, googleAuth); 64 | dispatch({ 65 | type: SET_POST_DETAILS, 66 | payload: res.data.post, 67 | }); 68 | }; 69 | 70 | export const addCommentAction = (comment, id, googleAuth) => async (dispatch) => { 71 | const res = await addCommentAPI(comment, id, googleAuth); 72 | dispatch({ 73 | type: SET_POST_DETAILS, 74 | payload: res.data.post, 75 | }); 76 | }; 77 | 78 | export const editPostACtion = (post, googleAuth) => async (dispatch) => { 79 | const res = await editPostAPI(post, googleAuth); 80 | dispatch({ 81 | type: EDIT_POST, 82 | payload: res.data.post, 83 | }); 84 | }; 85 | 86 | export const likePostAction = (post, googleAuth) => async (dispatch) => { 87 | const res = await likePostAPI(post, googleAuth); 88 | dispatch({ 89 | type: LIKE_POST, 90 | payload: res.data.post, 91 | }); 92 | }; 93 | 94 | export const deletePostAction = (post, googleAuth) => async (dispatch) => { 95 | const res = await deletePostAPI(post, googleAuth); 96 | dispatch({ 97 | type: DELETE_POST, 98 | payload: post._id, 99 | }); 100 | dispatch({ 101 | type: DECREASE_POST_COUNT, 102 | }); 103 | }; 104 | 105 | export const setEditPostIdAction = (id) => async (dispatch) => { 106 | dispatch({ 107 | type: EDIT_POST_ID, 108 | payload: id, 109 | }); 110 | }; 111 | 112 | export const serachPostAction = 113 | (titles, tags, googleAuth, page = 1) => 114 | async (dispatch) => { 115 | const res = await searchPostAPI(titles, tags, googleAuth, page); 116 | dispatch({ 117 | type: SEARCH_POST, 118 | payload: res.data.posts, // Will be changed 119 | }); 120 | dispatch({ 121 | type: SET_POST_COUNT, 122 | payload: res.data.totalCount, 123 | }); 124 | }; 125 | 126 | export const resetIdACtion = () => async (dispatch) => { 127 | dispatch({ 128 | type: RESET_ID, 129 | }); 130 | }; 131 | 132 | export const unexpectedErrorAction = (mes) => async (dispatch) => { 133 | dispatch({ 134 | type: mes, 135 | payload: { value: true, message: mes }, 136 | }); 137 | }; 138 | 139 | export const errorResetAction = () => async (dispatch) => { 140 | dispatch({ 141 | type: ERROR_RESET, 142 | payload: { value: false, message: "All is good" }, 143 | }); 144 | }; 145 | 146 | export const signUpAction = (user) => async (dispatch) => { 147 | const res = await signUpAPI(user); 148 | if (res?.data?.message !== "Sign up Successfully") return res; 149 | await dispatch({ 150 | type: SIGNUP, 151 | payload: { user: res.data.data.user, token: res.data.data.token }, 152 | }); 153 | return res; 154 | }; 155 | 156 | export const signInAction = (user) => async (dispatch) => { 157 | const res = await signInAPI(user); 158 | if (res?.data?.message !== "Sign in Successfully") return res; 159 | await dispatch({ 160 | type: SIGNIN, 161 | payload: { user: res.data.data.user, token: res.data.data.token }, 162 | }); 163 | return res; 164 | }; 165 | 166 | export const googleAuthAction = (profile, token) => async (dispatch) => { 167 | const res = await googleSigninAPI(token); 168 | if ( 169 | res?.data?.message !== "Sign up Successfully with Google" && 170 | res?.data?.message !== "Sign in Successfully with Google" 171 | ) 172 | return res; 173 | await dispatch({ 174 | type: GOOGLE_AUTH, 175 | payload: { profile, token, user: res.data.data.user }, 176 | }); 177 | }; 178 | 179 | export const updateGoogleAuthAction = (googleAuth) => async (dispatch) => { 180 | await dispatch({ 181 | type: UPDATE_GOOGLE_AUTH, 182 | payload: googleAuth, 183 | }); 184 | }; 185 | 186 | export const logOutAction = () => async (dispatch) => { 187 | await dispatch({ 188 | type: LOGOUT, 189 | }); 190 | }; 191 | -------------------------------------------------------------------------------- /client/src/Components/Posts/Post/Post.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable array-callback-return */ 2 | import React, { useState } from "react"; 3 | import Card from "react-bootstrap/Card"; 4 | import ListGroup from "react-bootstrap/ListGroup"; 5 | import { useDispatch, useSelector } from "react-redux"; 6 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 7 | import { 8 | faEllipsis, 9 | faThumbsUp as faThumbsUpSolid, 10 | faTrashAlt, 11 | } from "@fortawesome/free-solid-svg-icons"; 12 | import { faThumbsUp } from "@fortawesome/free-regular-svg-icons"; 13 | import Style from "./Post.module.scss"; 14 | import moment from "moment"; 15 | import { 16 | deletePostAction, 17 | likePostAction, 18 | setEditPostIdAction, 19 | } from "../../../Redux/Actions/actions"; 20 | import { useNavigate } from "react-router-dom"; 21 | 22 | function Post() { 23 | const posts = useSelector((state) => state.posts); 24 | const googleAuth = useSelector((state) => state.googleAuth); 25 | const dispatch = useDispatch(); 26 | const [waitingLike, setWaitingLike] = useState(false); 27 | const [waitingDelete, setWaitingDelete] = useState(false); 28 | const [deletedPostId, setdDletedPostId] = useState(""); 29 | const [editedPostId, setdEditedPostId] = useState(""); 30 | let navigate = useNavigate(); 31 | const postId = useSelector((state) => state.postId); 32 | 33 | return ( 34 | <> 35 |
36 | {posts.map((post, index) => { 37 | return ( 38 |
39 |
40 | 41 | { 46 | navigate(`/post/details/${post._id}`, { replace: true }); 47 | window.scrollTo({ 48 | top: 0, 49 | behavior: "smooth", 50 | }); 51 | }} 52 | /> 53 | 54 | 55 | {post.tags 56 | .map((value, index) => { 57 | return "#" + value; 58 | }) 59 | .join(" ")} 60 | 61 | {post.title} 62 | {post.message} 63 | 64 | 65 | 66 | 67 | 68 | {(!waitingLike || editedPostId !== post._id) && 69 | "Like "} 70 | {waitingLike && 71 | editedPostId === post._id && 72 | "Wait ... "} 73 | 74 | { 83 | setWaitingLike(true); 84 | setdEditedPostId(post._id); 85 | await dispatch(likePostAction(post, googleAuth)); 86 | setdEditedPostId(""); 87 | setWaitingLike(false); 88 | }} 89 | /> 90 | {post.likeCount.length} 91 | 92 | {post.creator === googleAuth.user._id && 93 | postId !== post._id && ( 94 | 95 | 96 | {(!waitingDelete || deletedPostId !== post._id) && 97 | "Delete "} 98 | {waitingDelete && 99 | deletedPostId === post._id && 100 | "Wait ... "} 101 | 102 | { 107 | setWaitingDelete(true); 108 | setdDletedPostId(post._id); 109 | await dispatch( 110 | deletePostAction(post, googleAuth) 111 | ); 112 | setdDletedPostId(""); 113 | setWaitingDelete(false); 114 | }} 115 | /> 116 | 117 | )} 118 | 119 | 120 | 123 | 124 | {post.postName} 125 | {post.creator === googleAuth.user._id && 126 | deletedPostId !== post._id && ( 127 | { 132 | dispatch(setEditPostIdAction(post._id)); 133 | }} 134 | /> 135 | )} 136 | 137 | {moment(post.createdAt).fromNow()} 138 | 139 | 140 |
141 |
142 | ); 143 | })} 144 |
145 | 146 | ); 147 | } 148 | 149 | export default Post; 150 | -------------------------------------------------------------------------------- /server/modules/users/controller/user-control.js: -------------------------------------------------------------------------------- 1 | // ====== --- ====== > Import Modules & Variables Declaration < ====== --- ====== // 2 | const users = require("../model/user-model"); 3 | const bcrypt = require("bcrypt"); 4 | var jwt = require("jsonwebtoken"); 5 | const { StatusCodes } = require("http-status-codes"); 6 | const { generatePassword } = require("./services"); 7 | 8 | // ====== --- ====== > User Methods < ====== --- ====== // 9 | 10 | /* 11 | //==// signUp: is the logic of '/signup' api that used to create new user with (name, email, password, age) fields. 12 | the response of this function in success (Sign up Successfully), in failure (show error message). 13 | */ 14 | const signUp = async (req, res) => { 15 | try { 16 | let { name, email, password, confirmPassword } = req.body; 17 | 18 | const oldUser = await users.findOne({ email, isDeleted: false }); 19 | if (!oldUser) { 20 | if (password === confirmPassword) { 21 | const newUser = new users({ name, email, password }); 22 | const data = await newUser.save(); 23 | 24 | var token = jwt.sign( 25 | { 26 | data: { name: data.name, email: data.email, role: data.role }, 27 | exp: Math.floor(Date.now() / 1000) + 60 * 60, 28 | }, 29 | process.env.ENCRYPT_KEY 30 | ); 31 | 32 | res.status(StatusCodes.CREATED).json({ 33 | message: "Sign up Successfully", 34 | data: { token, user: newUser }, 35 | }); 36 | } else { 37 | res 38 | .status(StatusCodes.BAD_REQUEST) 39 | .json({ message: "Password not matched confirm passwords" }); 40 | } 41 | } else { 42 | res 43 | .status(StatusCodes.BAD_REQUEST) 44 | .json({ message: "Email is Already Found" }); 45 | } 46 | } catch (error) { 47 | console.log(error); 48 | res.status(StatusCodes.INTERNAL_SERVER_ERROR).json(error); 49 | } 50 | }; 51 | 52 | /* 53 | //==// signin: is the logic of '/signin' api that used to sign in to website. 54 | the response of this function in success (Sign in Successfully), in failure (show error message). 55 | */ 56 | const signIn = async (req, res) => { 57 | try { 58 | let { email, password } = req.body; 59 | const oldUser = await users.findOne({ email, isDeleted: false }); 60 | if (oldUser) { 61 | let cdate = Date.now(); 62 | let match = bcrypt.compare( 63 | password, 64 | oldUser.password, 65 | function (err, result) { 66 | if (result) { 67 | var token = jwt.sign( 68 | { 69 | data: { 70 | name: oldUser.name, 71 | email: oldUser.email, 72 | role: oldUser.role, 73 | }, 74 | exp: Math.floor(cdate / 1000) + 60 * 60, 75 | }, 76 | process.env.ENCRYPT_KEY 77 | ); 78 | res.status(StatusCodes.OK).json({ 79 | message: "Sign in Successfully", 80 | data: { token, user: oldUser }, 81 | }); 82 | } else { 83 | res 84 | .status(StatusCodes.BAD_REQUEST) 85 | .json({ message: "Incorrect Password !" }); 86 | } 87 | } 88 | ); 89 | } else { 90 | res.status(StatusCodes.BAD_REQUEST).json({ message: "User Not Found !" }); 91 | } 92 | } catch (error) { 93 | console.log(error); 94 | res.status(StatusCodes.INTERNAL_SERVER_ERROR).json(error); 95 | } 96 | }; 97 | 98 | /* 99 | //==// Google signin: is the logic of '/google' api that used to continue with google. 100 | the response of this function in success (User login with google success), in failure (show error message). 101 | */ 102 | const googleSignIn = async (req, res) => { 103 | try { 104 | let { name, email } = req.body; 105 | const oldUser = await users.findOne({ email, isDeleted: false }); 106 | if (oldUser) { 107 | var token = jwt.sign( 108 | { 109 | data: { 110 | name: oldUser.name, 111 | email: oldUser.email, 112 | role: oldUser.role, 113 | }, 114 | exp: Math.floor(Date.now() / 1000) + 60 * 60, 115 | }, 116 | process.env.ENCRYPT_KEY 117 | ); 118 | res.status(StatusCodes.OK).json({ 119 | message: "Sign in Successfully with Google", 120 | data: { token, user: oldUser }, 121 | }); 122 | } else { 123 | const randomPassword = generatePassword(); 124 | const newUser = new users({ name, email, password: randomPassword }); 125 | const data = await newUser.save(); 126 | 127 | var token = jwt.sign( 128 | { 129 | data: { name: data.name, email: data.email, role: data.role }, 130 | exp: Math.floor(Date.now() / 1000) + 60 * 60, 131 | }, 132 | process.env.ENCRYPT_KEY 133 | ); 134 | 135 | res.status(StatusCodes.CREATED).json({ 136 | message: "Sign up Successfully with Google", 137 | data: { token, user: newUser }, 138 | }); 139 | } 140 | } catch (error) { 141 | console.log(error); 142 | res.status(StatusCodes.INTERNAL_SERVER_ERROR).json(error); 143 | } 144 | }; 145 | 146 | /* 147 | //==// Update password: is the logic of '/user-update-password' api that used to update user password. 148 | the response of this function in success (User updated Successfully), in failure (show error message). 149 | */ 150 | const updatePassword = async (req, res) => { 151 | try { 152 | let { oldPassword, newPassword } = req.body; 153 | let { email, role } = req.decoded; 154 | const oldUser = await users.findOne({ email, isDeleted: false }); 155 | if (oldUser) { 156 | let match = bcrypt.compare( 157 | oldPassword, 158 | oldUser.password, 159 | async function (err, result) { 160 | if (result) { 161 | let password = await bcrypt.hash(newPassword, 7); 162 | const data = await users.updateOne( 163 | { email, isDeleted: false }, 164 | { password } 165 | ); 166 | 167 | res 168 | .status(StatusCodes.OK) 169 | .json({ Message: "Password updated Successfully" }); 170 | } else { 171 | res 172 | .status(StatusCodes.BAD_REQUEST) 173 | .json({ Message: "Incorrect Password !" }); 174 | } 175 | } 176 | ); 177 | } else { 178 | res.status(StatusCodes.BAD_REQUEST).json({ Message: "User Not Found !" }); 179 | } 180 | } catch (error) { 181 | console.log(error); 182 | res.status(StatusCodes.INTERNAL_SERVER_ERROR).json(error); 183 | } 184 | }; 185 | 186 | // ====== --- ====== > Export Module < ====== --- ====== // 187 | module.exports = { 188 | signUp, 189 | signIn, 190 | googleSignIn, 191 | updatePassword, 192 | }; 193 | -------------------------------------------------------------------------------- /client/src/Components/AddPostForm/AddPostForm.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import Button from "react-bootstrap/Button"; 3 | import Form from "react-bootstrap/Form"; 4 | import { useDispatch, useSelector } from "react-redux"; 5 | import { 6 | addPostAction, 7 | editPostACtion, 8 | errorResetAction, 9 | resetIdACtion, 10 | unexpectedErrorAction, 11 | } from "../../Redux/Actions/actions"; 12 | import Alert from "react-bootstrap/Alert"; 13 | import { 14 | ERROR_ADD_POST, 15 | ERROR_EDIT_POST, 16 | } from "../../Redux/Actions/actionStrings"; 17 | 18 | function AddPostForm() { 19 | const dispatch = useDispatch(); 20 | const googleAuth = useSelector((state) => state.googleAuth); 21 | const error = useSelector((state) => state.error); 22 | const postId = useSelector((state) => state.postId); 23 | const [post, setPost] = useState({ 24 | postName: "", 25 | title: "", 26 | message: "", 27 | tags: [], 28 | file: "", 29 | }); 30 | const posts = useSelector((state) => state.posts); 31 | const [waiting, setWaiting] = useState(false); 32 | const [editState, setEditState] = useState(false); 33 | 34 | if (postId && editState === false) { 35 | setPost(posts.find((item) => item._id === postId)); 36 | setEditState(true); 37 | } 38 | 39 | const savePostData = ({ target }) => { 40 | if (target.name !== "tags") 41 | setPost({ ...post, [target.name]: target.value }); 42 | else setPost({ ...post, [target.name]: target.value.split(" ") }); 43 | }; 44 | 45 | const clearForm = (e) => { 46 | e.preventDefault(); 47 | setPost({ 48 | postName: "", 49 | title: "", 50 | message: "", 51 | tags: [], 52 | file: "", 53 | }); 54 | 55 | document.getElementById("postForm").reset(); 56 | }; 57 | 58 | const submitPost = async (e) => { 59 | e.preventDefault(); 60 | setWaiting(true); 61 | if (!editState) { 62 | let result = Object.values(post).every((p) => { 63 | if (typeof p === "string") return p !== "" && !/^\s/.test(p); 64 | else return p[0] !== ""; 65 | }); 66 | if (result) { 67 | await dispatch(errorResetAction()); 68 | await dispatch(addPostAction(post, googleAuth)); 69 | clearForm(e); 70 | } else await dispatch(unexpectedErrorAction(ERROR_ADD_POST)); 71 | } else { 72 | let result = Object.values(post).every((p) => { 73 | if (typeof p === "string") return p !== "" && !/^\s/.test(p); 74 | else return p[0] !== ""; 75 | }); 76 | if (result) { 77 | await dispatch(errorResetAction()); 78 | await dispatch(editPostACtion(post, googleAuth)); 79 | dispatch(resetIdACtion()); 80 | setEditState(false); 81 | clearForm(e); 82 | } else await dispatch(unexpectedErrorAction(ERROR_EDIT_POST)); 83 | } 84 | setWaiting(false); 85 | }; 86 | 87 | const getBase64 = ({ target }, cb) => { 88 | let reader = new FileReader(); 89 | if (target.files[0] && target.files[0].type.match("image.*")) 90 | reader.readAsDataURL(target.files[0]); 91 | else target.value = ""; 92 | reader.onload = function () { 93 | cb(reader.result); 94 | }; 95 | reader.onerror = function (error) { 96 | console.log("Error: ", error); 97 | }; 98 | }; 99 | 100 | return ( 101 | <> 102 |
103 |

104 | {editState && "Editing a Memory"} 105 | {!editState && "Creating a Memory"} 106 |

107 |
108 | 109 | 117 | 118 | 119 | 120 | 128 | 129 | 130 | 131 | 139 | 140 | 141 | 142 | 150 | 151 | 152 | 153 | {!editState && ( 154 | { 156 | getBase64(e, (result) => { 157 | setPost({ 158 | ...post, 159 | [e.target.name]: result, 160 | filePath: e.target.value, 161 | }); 162 | }); 163 | }} 164 | type="file" 165 | name="file" 166 | required={true} 167 | /> 168 | )} 169 | 170 | 171 | {error.value && error.type === "post" && ( 172 | 173 | {error.message} 174 | 175 | )} 176 | 177 | 188 | {!editState && ( 189 | 197 | )} 198 | {editState && ( 199 | 207 | )} 208 |
209 |
210 | 211 | ); 212 | } 213 | 214 | export default AddPostForm; 215 | -------------------------------------------------------------------------------- /client/src/Components/PostDetails/PostDetails.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import Card from "react-bootstrap/Card"; 3 | import ListGroup from "react-bootstrap/ListGroup"; 4 | import { useSelector, useDispatch } from "react-redux"; 5 | import { useNavigate } from "react-router-dom"; 6 | import { 7 | addCommentAction, 8 | errorResetAction, 9 | getPostAction, 10 | serachPostAction, 11 | unexpectedErrorAction, 12 | } from "../../Redux/Actions/actions"; 13 | import { useParams } from "react-router-dom"; 14 | import moment from "moment"; 15 | import Button from "react-bootstrap/Button"; 16 | import Form from "react-bootstrap/Form"; 17 | import { ERROR_SEND_COMMENT } from "../../Redux/Actions/actionStrings"; 18 | import Alert from "react-bootstrap/Alert"; 19 | import { useRef } from "react"; 20 | 21 | function PostDetails() { 22 | const dispatch = useDispatch(); 23 | const error = useSelector((state) => state.error); 24 | const postDetails = useSelector((state) => state.postDetails); 25 | const memoryProfile = JSON.parse(localStorage.getItem("memoryProfile")); 26 | const [waiting, setWaiting] = useState(true); 27 | const [waitingBtn, setWaitingBtn] = useState(false); 28 | const [comment, setComment] = useState(""); 29 | const [comments, setComments] = useState(postDetails.comments); 30 | let navigate = useNavigate(); 31 | const commentRef = useRef(); 32 | const { id } = useParams(); 33 | const posts = useSelector((state) => state.posts).filter((value) => { 34 | return value._id !== postDetails._id; 35 | }); 36 | 37 | const fetchMyAPI = async () => { 38 | try { 39 | setWaiting(true); 40 | await dispatch(getPostAction(id, memoryProfile)); 41 | } catch (error) { 42 | localStorage.clear(); 43 | navigate("/auth", { replace: true }); 44 | } 45 | }; 46 | 47 | useEffect(() => { 48 | const func = async () => { 49 | setComments(postDetails.comments); 50 | setWaiting(true); 51 | await dispatch( 52 | serachPostAction(postDetails.tags, postDetails.tags, memoryProfile, 1) 53 | ); 54 | if (Object.keys(postDetails).length !== 0) setWaiting(false); 55 | }; 56 | func(); 57 | }, [postDetails._id]); 58 | 59 | useEffect(() => { 60 | fetchMyAPI(); 61 | }, [id]); 62 | 63 | const handleComment = ({ target }) => { 64 | setComment(target.value); 65 | }; 66 | 67 | const sendComment = async (e) => { 68 | e.preventDefault(); 69 | if (comment !== "" && !/^\s/.test(comment)) { 70 | setComments([...comments, `${memoryProfile.user.name}: ${comment}`]); 71 | setWaitingBtn(true); 72 | await dispatch(errorResetAction()); 73 | await dispatch( 74 | addCommentAction( 75 | `${memoryProfile.user.name}: ${comment}`, 76 | postDetails._id, 77 | memoryProfile 78 | ) 79 | ); 80 | commentRef.current.scrollIntoView({ behavior: "smooth" }); 81 | setWaitingBtn(false); 82 | setComment(""); 83 | } else await dispatch(unexpectedErrorAction(ERROR_SEND_COMMENT)); 84 | }; 85 | 86 | return ( 87 | <> 88 |
89 | {waiting &&

Wait ...

} 90 | {!waiting && ( 91 |
92 |
93 | 94 | 95 |

{postDetails.title}

96 |
97 |
98 | 99 | {postDetails.tags 100 | .map((value, index) => { 101 | return "#" + value; 102 | }) 103 | .join(" ")} 104 | 105 |

{postDetails.message}

106 |
Created by: {postDetails.postName}
107 |

{moment(postDetails.createdAt).fromNow()}

108 | 109 | 110 |
Realtime Chat - comming soon !
111 |
112 | 113 |
114 |
115 | 116 | 117 |
Comments
118 |
119 | 120 |
124 | {comments.map((c, i) => { 125 | return {c}; 126 | })} 127 |
128 |
129 |
130 |
131 |
132 |
133 |
Write a comment
134 |
135 | 139 | 145 | 146 | {error.value && error.type === "comment" && ( 147 | 148 | {error.message} 149 | 150 | )} 151 |
152 | 163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 | 174 |
175 | 176 |
177 |
178 | {posts.map((value) => { 179 | return ( 180 |
181 | 182 | { 185 | navigate(`/post/details/${value._id}`, { 186 | replace: true, 187 | }); 188 | window.scrollTo({ 189 | top: 0, 190 | behavior: "smooth", 191 | }); 192 | }} 193 | variant="top" 194 | src={value.file} 195 | /> 196 | 197 | {value.title} 198 | {value.message} 199 | 200 | 201 |
202 | ); 203 | })} 204 |
205 |
206 |
207 | )} 208 |
209 | 210 | ); 211 | } 212 | 213 | export default PostDetails; 214 | -------------------------------------------------------------------------------- /client/src/Components/Authentication/Authentication.jsx: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import React, { useEffect, useState } from "react"; 3 | import Button from "react-bootstrap/Button"; 4 | import Form from "react-bootstrap/Form"; 5 | import Alert from "react-bootstrap/Alert"; 6 | import { Navigate, useNavigate } from "react-router-dom"; 7 | import Style from "./Authentication.module.scss"; 8 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 9 | import { faEyeSlash, faEye, faG } from "@fortawesome/free-solid-svg-icons"; 10 | import { GoogleLogin } from "react-google-login"; 11 | import { gapi } from "gapi-script"; 12 | import { 13 | errorResetAction, 14 | googleAuthAction, 15 | signInAction, 16 | signUpAction, 17 | unexpectedErrorAction, 18 | } from "../../Redux/Actions/actions"; 19 | import { useDispatch, useSelector } from "react-redux"; 20 | import { ERROR_SIGNIN, ERROR_SIGNUP } from "../../Redux/Actions/actionStrings"; 21 | 22 | function Authentication() { 23 | const dispatch = useDispatch(); 24 | const navigate = useNavigate(); 25 | useEffect(() => { 26 | function start() { 27 | gapi.client.init({ 28 | clientId: 29 | "633147263244-m08i8j19dacnbs07mjjqlflti4lhak2c.apps.googleusercontent.com", 30 | scope: "email", 31 | }); 32 | } 33 | 34 | gapi.load("client:auth2", start); 35 | }, []); 36 | 37 | const error = useSelector((state) => state.error); 38 | let [isSignIn, setIsSignIn] = useState(true); 39 | let [user, setUser] = useState({ 40 | first_name: "", 41 | last_name: "", 42 | email: "", 43 | password: "", 44 | confirmPassword: "", 45 | }); 46 | let [waiting, setWaiting] = useState(false); 47 | let [success, setSuccess] = useState(false); 48 | let [passwordShown, setPasswordShown] = useState(false); 49 | 50 | let togglePassword = () => { 51 | setPasswordShown(!passwordShown); 52 | }; 53 | 54 | const getUser = ({ target }) => { 55 | setUser((prevUser) => { 56 | return { ...prevUser, [target.name]: target.value }; 57 | }); 58 | }; 59 | 60 | const sendData = async (e) => { 61 | e.preventDefault(); 62 | setWaiting(true); 63 | 64 | if (isSignIn) { 65 | // ====== ---- ====== Signin ====== ---- ====== // 66 | let result = 67 | user.email !== "" && 68 | !/^\s/.test(user.emai) && 69 | user.password !== "" && 70 | !/^\s/.test(user.password); 71 | if (result) { 72 | const res = await dispatch( 73 | signInAction({ 74 | email: user.email, 75 | password: user.password, 76 | confirmPassword: user.confirmPassword, 77 | name: `${user.first_name} ${user.last_name}`, 78 | }) 79 | ); 80 | if (res?.data?.message === "Sign in Successfully") { 81 | setSuccess(true); 82 | await dispatch(errorResetAction()); 83 | navigate("/posts", { replace: true }); 84 | } else { 85 | await dispatch(unexpectedErrorAction(ERROR_SIGNIN)); 86 | } 87 | } else { 88 | await dispatch(unexpectedErrorAction(ERROR_SIGNIN)); 89 | } 90 | setWaiting(false); 91 | } else { 92 | // ====== ---- ====== Signup ====== ---- ====== // 93 | let result = Object.values(user).every((p) => { 94 | return p !== "" && !/^\s/.test(p); 95 | }); 96 | 97 | if (result) { 98 | const res = await dispatch( 99 | signUpAction({ 100 | email: user.email, 101 | password: user.password, 102 | confirmPassword: user.confirmPassword, 103 | name: `${user.first_name} ${user.last_name}`, 104 | }) 105 | ); 106 | if (res?.data?.message === "Sign up Successfully") { 107 | setSuccess(true); 108 | await dispatch(errorResetAction()); 109 | setWaiting(false); 110 | navigate("/posts", { replace: true }); 111 | } else { 112 | setWaiting(false); 113 | await dispatch(unexpectedErrorAction(ERROR_SIGNUP)); 114 | } 115 | } else { 116 | await dispatch(unexpectedErrorAction(ERROR_SIGNUP)); 117 | } 118 | setWaiting(false); 119 | } 120 | }; 121 | 122 | const responseGoogleSuccess = async (res) => { 123 | console.log("Google Sign Up success"); 124 | const profile = res?.profileObj; 125 | const token = res?.tokenId; 126 | await dispatch(googleAuthAction(profile, token)); 127 | await navigate("/posts", { replace: true }); 128 | }; 129 | 130 | const responseGoogleFailure = async (error) => { 131 | console.log("Google Sign up failure"); 132 | console.log(error); 133 | }; 134 | 135 | return ( 136 | <> 137 |
138 |
139 |
140 | {!isSignIn && ( 141 | 142 | 149 | 150 | )} 151 | {!isSignIn && ( 152 | 153 | 160 | 161 | )} 162 | 163 | 170 | 171 | 175 | 182 | 183 | 189 | 190 | {!isSignIn && ( 191 | 195 | 202 | 203 | 209 | 210 | )} 211 | {error.value && error.type === "auth" && ( 212 | 213 | {error.message} 214 | 215 | )} 216 | 221 | 222 | ( 225 | 234 | )} 235 | buttonText="Login" 236 | onSuccess={responseGoogleSuccess} 237 | onFailure={responseGoogleFailure} 238 | cookiePolicy={"single_host_origin"} 239 | /> 240 | 241 | {isSignIn && ( 242 | 243 | <> 244 | Don't have an account ?{" "} 245 | { 248 | setIsSignIn(false); 249 | }} 250 | > 251 | Sign Up 252 | 253 | 254 | 255 | )} 256 | {!isSignIn && ( 257 | 258 | <> 259 | Already have an account ?{" "} 260 | { 263 | setIsSignIn(true); 264 | }} 265 | > 266 | Sign In 267 | 268 | 269 | 270 | )} 271 | 272 |
273 |
274 | 275 | ); 276 | } 277 | 278 | export default Authentication; 279 | -------------------------------------------------------------------------------- /server/modules/posts/controller/post-control.js: -------------------------------------------------------------------------------- 1 | // ====== --- ====== > Import Modules & Variables Declaration < ====== --- ====== // 2 | const posts = require("../model/post-model"); 3 | const users = require("../../users/model/user-model"); 4 | const bcrypt = require("bcrypt"); 5 | var jwt = require("jsonwebtoken"); 6 | const { StatusCodes } = require("http-status-codes"); 7 | 8 | // ====== --- ====== > Post Methods < ====== --- ====== // 9 | 10 | /* 11 | //==// Add Post: is the logic of '/post/add' api that used to create new post with required fields. 12 | the response of this function in success (Post created successfully), in failure (show error message). 13 | */ 14 | 15 | const addPost = async (req, res) => { 16 | try { 17 | let { postName, title, message, tags, file, filePath } = req.body; 18 | let { email } = req.decoded; 19 | let createdAt = new Date(); 20 | 21 | const oldUser = await users.findOne({ email, isDeleted: false }); 22 | if (oldUser) { 23 | let newPost = new posts({ 24 | creator: oldUser._id, 25 | postName, 26 | title, 27 | message, 28 | tags, 29 | file, 30 | filePath, 31 | createdAt, 32 | }); 33 | await newPost.save(); 34 | res 35 | .status(StatusCodes.CREATED) 36 | .json({ message: "Post created successfully", post: newPost }); 37 | } else 38 | res.status(StatusCodes.BAD_REQUEST).json({ message: "User not found" }); 39 | } catch (error) { 40 | console.log({ error }); 41 | res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ error }); 42 | } 43 | }; 44 | 45 | /* 46 | //==// Get Posts: is the logic of '/posts' api that used to get dashboard posts. 47 | the response of this function in success (data:posts), in failure (show error message). 48 | */ 49 | const getPosts = async (req, res) => { 50 | try { 51 | let { page } = req.query; 52 | page = Number(page); 53 | 54 | const limit = 10; 55 | let startIndex = (page - 1) * limit; 56 | 57 | const totalCount = await posts.countDocuments({}); 58 | 59 | const data = await posts 60 | .find({ isDeleted: false }) 61 | .sort({ _id: -1 }) 62 | .limit(limit) 63 | .skip(startIndex); 64 | res 65 | .status(StatusCodes.OK) 66 | .json({ message: "Success", posts: data, totalCount }); 67 | } catch (error) { 68 | console.log({ error }); 69 | res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ error }); 70 | } 71 | }; 72 | 73 | /* 74 | //==// Edit Post: is the logic of '/post/edit/id' api that used to edit specific post. 75 | the response of this function in success (data:post), in failure (show error message). 76 | */ 77 | const editPost = async (req, res) => { 78 | try { 79 | let { postName, title, message, tags, userId } = req.body; 80 | let { id } = req.params; 81 | let { email } = req.decoded; 82 | const oldUser = await users.findOne({ email, isDeleted: false }); 83 | if (oldUser) { 84 | if (oldUser._id == userId) { 85 | const data = await posts.findByIdAndUpdate( 86 | id, 87 | { 88 | postName, 89 | title, 90 | message, 91 | tags, 92 | }, 93 | { 94 | new: true, 95 | } 96 | ); 97 | res.status(StatusCodes.OK).json({ message: "Success", post: data }); 98 | } else 99 | res.status(StatusCodes.BAD_REQUEST).json({ message: "Not Post Owner" }); 100 | } else 101 | res.status(StatusCodes.BAD_REQUEST).json({ message: "User not found" }); 102 | } catch (error) { 103 | console.log({ error }); 104 | res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ error }); 105 | } 106 | }; 107 | 108 | /* 109 | //==// Delete Post: is the logic of '/post/delete/:id' api that used to delete specific post. 110 | the response of this function in success ("Post deleted successfully"), in failure (show error message). 111 | */ 112 | const deletePost = async (req, res) => { 113 | try { 114 | let { userId } = req.body; 115 | let { email } = req.decoded; 116 | let { id } = req.params; 117 | 118 | const oldUser = await users.findOne({ email, isDeleted: false }); 119 | if (oldUser) { 120 | if (oldUser._id == userId) { 121 | const data = await posts.findByIdAndDelete(id); 122 | res 123 | .status(StatusCodes.OK) 124 | .json({ message: "Post deleted successfully", post: data }); 125 | } else 126 | res.status(StatusCodes.BAD_REQUEST).json({ message: "Not Post Owner" }); 127 | } else 128 | res.status(StatusCodes.BAD_REQUEST).json({ message: "User not found" }); 129 | } catch (error) { 130 | console.log({ error }); 131 | res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ error }); 132 | } 133 | }; 134 | 135 | /* 136 | //==// Like Post: is the logic of '/post/like/:id' api that used to Like specific post. 137 | the response of this function in success (updated post), in failure (show error message). 138 | */ 139 | const likePost = async (req, res) => { 140 | try { 141 | let { id } = req.params; 142 | console.log(req.decoded); 143 | let { email } = req.decoded; 144 | 145 | const oldUser = await users.findOne({ email, isDeleted: false }); 146 | if (oldUser) { 147 | const oldPost = await posts.findById(id); 148 | if (oldPost) { 149 | let likes = oldPost.likeCount; 150 | if (likes.includes(oldUser._id)) { 151 | let index = likes.indexOf(oldUser._id); 152 | likes.splice(index, 1); 153 | } else likes.push(oldUser._id); 154 | const data = await posts.findByIdAndUpdate( 155 | id, 156 | { 157 | likeCount: likes, 158 | }, 159 | { 160 | new: true, 161 | } 162 | ); 163 | res 164 | .status(StatusCodes.OK) 165 | .json({ message: "Post Liked successfully", post: data }); 166 | } else 167 | res 168 | .status(StatusCodes.BAD_REQUEST) 169 | .json({ message: "Post not found", post: "" }); 170 | } else 171 | res 172 | .status(StatusCodes.BAD_REQUEST) 173 | .json({ message: "User not found", post: "" }); 174 | } catch (error) { 175 | console.log({ error }); 176 | res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ error }); 177 | } 178 | }; 179 | 180 | /* 181 | //==// Search Post: is the logic of '/post/search/?searchQuery='value'&tags='value' api that used to search for specific post. 182 | the response of this function in success (posts), in failure (show error message). 183 | */ 184 | const searchPost = async (req, res) => { 185 | try { 186 | let { titles, tags, page } = req.query; 187 | let { email } = req.decoded; 188 | 189 | page = Number(page); 190 | const limit = 10; 191 | let startIndex = (page - 1) * limit; 192 | 193 | tags = tags.split(","); 194 | titles = titles.split(","); 195 | 196 | console.log({ titles }); 197 | console.log({ tags }); 198 | 199 | let insensitiveTitles = []; 200 | titles.forEach(function (item) { 201 | var re = new RegExp(item, "i"); 202 | insensitiveTitles.push(re); 203 | }); 204 | 205 | let insensitiveTags = []; 206 | tags.forEach(function (item) { 207 | var re = new RegExp(item, "i"); 208 | insensitiveTags.push(re); 209 | }); 210 | 211 | const oldUser = await users.findOne({ email, isDeleted: false }); 212 | if (oldUser) { 213 | const totalCount = await posts.countDocuments({ 214 | $or: [ 215 | { title: { $in: insensitiveTitles } }, 216 | { tags: { $in: insensitiveTags } }, 217 | ], 218 | }); 219 | 220 | const data = await posts 221 | .find({ 222 | $or: [ 223 | { title: { $in: insensitiveTitles } }, 224 | { tags: { $in: insensitiveTags } }, 225 | ], 226 | }) 227 | .sort({ _id: -1 }) 228 | .limit(limit) 229 | .skip(startIndex); 230 | 231 | res 232 | .status(StatusCodes.OK) 233 | .json({ message: "Found Posts", posts: data, totalCount }); 234 | } else 235 | res 236 | .status(StatusCodes.BAD_REQUEST) 237 | .json({ message: "User not found", post: "" }); 238 | } catch (error) { 239 | console.log(error); 240 | res.status(StatusCodes.INTERNAL_SERVER_ERROR).json(error); 241 | } 242 | }; 243 | 244 | /* 245 | //==// Get Post: is the logic of '/post/:id' api that used to get specific post by id. 246 | the response of this function in success (data:post), in failure (show error message). 247 | */ 248 | const getPost = async (req, res) => { 249 | try { 250 | let { id } = req.params; 251 | let { email } = req.decoded; 252 | const oldUser = await users.findOne({ email, isDeleted: false }); 253 | if (oldUser) { 254 | const data = await posts.findOne({ _id: id, isDeleted: false }); 255 | res.status(StatusCodes.OK).json({ message: "Found Posts", post: data }); 256 | } else 257 | res.status(StatusCodes.BAD_REQUEST).json({ message: "User not found" }); 258 | } catch (error) { 259 | console.log({ error }); 260 | res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ error }); 261 | } 262 | }; 263 | 264 | /* 265 | //==// Add Comment: is the logic of '/post/comment/:id' api that used to add comment to specific post. 266 | the response of this function in success (data:post), in failure (show error message). 267 | */ 268 | 269 | const addComment = async (req, res) => { 270 | try { 271 | let { id } = req.params; 272 | let { email } = req.decoded; 273 | let { comment } = req.body; 274 | 275 | const oldUser = await users.findOne({ email, isDeleted: false }); 276 | if (oldUser) { 277 | const oldPost = await posts.findById(id); 278 | if (oldPost) { 279 | let comments = oldPost?.comments || []; 280 | comments.push(comment); 281 | const data = await posts.findByIdAndUpdate( 282 | id, 283 | { 284 | comments, 285 | }, 286 | { 287 | new: true, 288 | } 289 | ); 290 | res 291 | .status(StatusCodes.OK) 292 | .json({ message: "Add Comment Success", post: data }); 293 | } else 294 | res 295 | .status(StatusCodes.BAD_REQUEST) 296 | .json({ message: "Post not found", post: "" }); 297 | } else 298 | res 299 | .status(StatusCodes.BAD_REQUEST) 300 | .json({ message: "User not found", post: "" }); 301 | } catch (error) { 302 | console.log({ error }); 303 | res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ error }); 304 | } 305 | }; 306 | 307 | // ====== --- ====== > Export Module < ====== --- ====== // 308 | module.exports = { 309 | addPost, 310 | getPosts, 311 | editPost, 312 | deletePost, 313 | likePost, 314 | searchPost, 315 | getPost, 316 | addComment, 317 | }; 318 | -------------------------------------------------------------------------------- /server/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@hapi/hoek@^9.0.0": 6 | version "9.3.0" 7 | resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb" 8 | integrity sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ== 9 | 10 | "@hapi/topo@^5.0.0": 11 | version "5.1.0" 12 | resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.1.0.tgz#dc448e332c6c6e37a4dc02fd84ba8d44b9afb012" 13 | integrity sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg== 14 | dependencies: 15 | "@hapi/hoek" "^9.0.0" 16 | 17 | "@mapbox/node-pre-gyp@^1.0.0": 18 | version "1.0.9" 19 | resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.9.tgz#09a8781a3a036151cdebbe8719d6f8b25d4058bc" 20 | integrity sha512-aDF3S3rK9Q2gey/WAttUlISduDItz5BU3306M9Eyv6/oS40aMprnopshtlKTykxRNIBEZuRMaZAnbrQ4QtKGyw== 21 | dependencies: 22 | detect-libc "^2.0.0" 23 | https-proxy-agent "^5.0.0" 24 | make-dir "^3.1.0" 25 | node-fetch "^2.6.7" 26 | nopt "^5.0.0" 27 | npmlog "^5.0.1" 28 | rimraf "^3.0.2" 29 | semver "^7.3.5" 30 | tar "^6.1.11" 31 | 32 | "@sideway/address@^4.1.3": 33 | version "4.1.4" 34 | resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.4.tgz#03dccebc6ea47fdc226f7d3d1ad512955d4783f0" 35 | integrity sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw== 36 | dependencies: 37 | "@hapi/hoek" "^9.0.0" 38 | 39 | "@sideway/formula@^3.0.0": 40 | version "3.0.0" 41 | resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.0.tgz#fe158aee32e6bd5de85044be615bc08478a0a13c" 42 | integrity sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg== 43 | 44 | "@sideway/pinpoint@^2.0.0": 45 | version "2.0.0" 46 | resolved "https://registry.yarnpkg.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df" 47 | integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ== 48 | 49 | "@types/node@*": 50 | version "18.7.2" 51 | resolved "https://registry.yarnpkg.com/@types/node/-/node-18.7.2.tgz#22306626110c459aedd2cdf131c749ec781e3b34" 52 | integrity sha512-ce7MIiaYWCFv6A83oEultwhBXb22fxwNOQf5DIxWA4WXvDQ7K+L0fbWl/YOfCzlR5B/uFkSnVBhPcOfOECcWvA== 53 | 54 | "@types/webidl-conversions@*": 55 | version "6.1.1" 56 | resolved "https://registry.yarnpkg.com/@types/webidl-conversions/-/webidl-conversions-6.1.1.tgz#e33bc8ea812a01f63f90481c666334844b12a09e" 57 | integrity sha512-XAahCdThVuCFDQLT7R7Pk/vqeObFNL3YqRyFZg+AqAP/W1/w3xHaIxuW7WszQqTbIBOPRcItYJIou3i/mppu3Q== 58 | 59 | "@types/whatwg-url@^8.2.1": 60 | version "8.2.2" 61 | resolved "https://registry.yarnpkg.com/@types/whatwg-url/-/whatwg-url-8.2.2.tgz#749d5b3873e845897ada99be4448041d4cc39e63" 62 | integrity sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA== 63 | dependencies: 64 | "@types/node" "*" 65 | "@types/webidl-conversions" "*" 66 | 67 | abbrev@1: 68 | version "1.1.1" 69 | resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" 70 | integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== 71 | 72 | accepts@~1.3.8: 73 | version "1.3.8" 74 | resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" 75 | integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== 76 | dependencies: 77 | mime-types "~2.1.34" 78 | negotiator "0.6.3" 79 | 80 | agent-base@6: 81 | version "6.0.2" 82 | resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" 83 | integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== 84 | dependencies: 85 | debug "4" 86 | 87 | ansi-regex@^5.0.1: 88 | version "5.0.1" 89 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" 90 | integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== 91 | 92 | anymatch@~3.1.2: 93 | version "3.1.2" 94 | resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" 95 | integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== 96 | dependencies: 97 | normalize-path "^3.0.0" 98 | picomatch "^2.0.4" 99 | 100 | "aproba@^1.0.3 || ^2.0.0": 101 | version "2.0.0" 102 | resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" 103 | integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== 104 | 105 | are-we-there-yet@^2.0.0: 106 | version "2.0.0" 107 | resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz#372e0e7bd279d8e94c653aaa1f67200884bf3e1c" 108 | integrity sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw== 109 | dependencies: 110 | delegates "^1.0.0" 111 | readable-stream "^3.6.0" 112 | 113 | array-flatten@1.1.1: 114 | version "1.1.1" 115 | resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" 116 | integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== 117 | 118 | balanced-match@^1.0.0: 119 | version "1.0.2" 120 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" 121 | integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== 122 | 123 | base64-js@^1.3.1: 124 | version "1.5.1" 125 | resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" 126 | integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== 127 | 128 | bcrypt@^5.0.1: 129 | version "5.0.1" 130 | resolved "https://registry.yarnpkg.com/bcrypt/-/bcrypt-5.0.1.tgz#f1a2c20f208e2ccdceea4433df0c8b2c54ecdf71" 131 | integrity sha512-9BTgmrhZM2t1bNuDtrtIMVSmmxZBrJ71n8Wg+YgdjHuIWYF7SjjmCPZFB+/5i/o/PIeRpwVJR3P+NrpIItUjqw== 132 | dependencies: 133 | "@mapbox/node-pre-gyp" "^1.0.0" 134 | node-addon-api "^3.1.0" 135 | 136 | binary-extensions@^2.0.0: 137 | version "2.2.0" 138 | resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" 139 | integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== 140 | 141 | body-parse@^0.1.0: 142 | version "0.1.0" 143 | resolved "https://registry.yarnpkg.com/body-parse/-/body-parse-0.1.0.tgz#cd1639d9ac0725be81db38e1ca4012476f7051a6" 144 | integrity sha512-k0PDF7vZZpspXlwoM8ywh9PIHZokooS0Rek4M8Vekoro7XuuaWVhjgTpdzIRrfKj5oLQahwjn621/4kG4d91xw== 145 | 146 | body-parser@1.20.0: 147 | version "1.20.0" 148 | resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.0.tgz#3de69bd89011c11573d7bfee6a64f11b6bd27cc5" 149 | integrity sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg== 150 | dependencies: 151 | bytes "3.1.2" 152 | content-type "~1.0.4" 153 | debug "2.6.9" 154 | depd "2.0.0" 155 | destroy "1.2.0" 156 | http-errors "2.0.0" 157 | iconv-lite "0.4.24" 158 | on-finished "2.4.1" 159 | qs "6.10.3" 160 | raw-body "2.5.1" 161 | type-is "~1.6.18" 162 | unpipe "1.0.0" 163 | 164 | brace-expansion@^1.1.7: 165 | version "1.1.11" 166 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 167 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== 168 | dependencies: 169 | balanced-match "^1.0.0" 170 | concat-map "0.0.1" 171 | 172 | braces@~3.0.2: 173 | version "3.0.2" 174 | resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" 175 | integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== 176 | dependencies: 177 | fill-range "^7.0.1" 178 | 179 | bson@^4.6.5: 180 | version "4.6.5" 181 | resolved "https://registry.yarnpkg.com/bson/-/bson-4.6.5.tgz#1a410148c20eef4e40d484878a037a7036e840fb" 182 | integrity sha512-uqrgcjyOaZsHfz7ea8zLRCLe1u+QGUSzMZmvXqO24CDW7DWoW1qiN9folSwa7hSneTSgM2ykDIzF5kcQQ8cwNw== 183 | dependencies: 184 | buffer "^5.6.0" 185 | 186 | buffer-equal-constant-time@1.0.1: 187 | version "1.0.1" 188 | resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" 189 | integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA== 190 | 191 | buffer@^5.6.0: 192 | version "5.7.1" 193 | resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" 194 | integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== 195 | dependencies: 196 | base64-js "^1.3.1" 197 | ieee754 "^1.1.13" 198 | 199 | bytes@3.1.2: 200 | version "3.1.2" 201 | resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" 202 | integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== 203 | 204 | call-bind@^1.0.0: 205 | version "1.0.2" 206 | resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" 207 | integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== 208 | dependencies: 209 | function-bind "^1.1.1" 210 | get-intrinsic "^1.0.2" 211 | 212 | chokidar@^3.5.2: 213 | version "3.5.3" 214 | resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" 215 | integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== 216 | dependencies: 217 | anymatch "~3.1.2" 218 | braces "~3.0.2" 219 | glob-parent "~5.1.2" 220 | is-binary-path "~2.1.0" 221 | is-glob "~4.0.1" 222 | normalize-path "~3.0.0" 223 | readdirp "~3.6.0" 224 | optionalDependencies: 225 | fsevents "~2.3.2" 226 | 227 | chownr@^2.0.0: 228 | version "2.0.0" 229 | resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" 230 | integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== 231 | 232 | color-support@^1.1.2: 233 | version "1.1.3" 234 | resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" 235 | integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== 236 | 237 | concat-map@0.0.1: 238 | version "0.0.1" 239 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 240 | integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== 241 | 242 | console-control-strings@^1.0.0, console-control-strings@^1.1.0: 243 | version "1.1.0" 244 | resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" 245 | integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== 246 | 247 | content-disposition@0.5.4: 248 | version "0.5.4" 249 | resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" 250 | integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== 251 | dependencies: 252 | safe-buffer "5.2.1" 253 | 254 | content-type@~1.0.4: 255 | version "1.0.4" 256 | resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" 257 | integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== 258 | 259 | cookie-signature@1.0.6: 260 | version "1.0.6" 261 | resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" 262 | integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== 263 | 264 | cookie@0.5.0: 265 | version "0.5.0" 266 | resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" 267 | integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== 268 | 269 | cors@^2.8.5: 270 | version "2.8.5" 271 | resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" 272 | integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== 273 | dependencies: 274 | object-assign "^4" 275 | vary "^1" 276 | 277 | debug@2.6.9: 278 | version "2.6.9" 279 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" 280 | integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== 281 | dependencies: 282 | ms "2.0.0" 283 | 284 | debug@4, debug@4.x, debug@^4.3.3: 285 | version "4.3.4" 286 | resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" 287 | integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== 288 | dependencies: 289 | ms "2.1.2" 290 | 291 | debug@^3.2.7: 292 | version "3.2.7" 293 | resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" 294 | integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== 295 | dependencies: 296 | ms "^2.1.1" 297 | 298 | delegates@^1.0.0: 299 | version "1.0.0" 300 | resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" 301 | integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ== 302 | 303 | denque@^2.0.1: 304 | version "2.1.0" 305 | resolved "https://registry.yarnpkg.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1" 306 | integrity sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw== 307 | 308 | depd@2.0.0: 309 | version "2.0.0" 310 | resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" 311 | integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== 312 | 313 | destroy@1.2.0: 314 | version "1.2.0" 315 | resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" 316 | integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== 317 | 318 | detect-libc@^2.0.0: 319 | version "2.0.1" 320 | resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.1.tgz#e1897aa88fa6ad197862937fbc0441ef352ee0cd" 321 | integrity sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w== 322 | 323 | dotenv@^16.0.0: 324 | version "16.0.1" 325 | resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.1.tgz#8f8f9d94876c35dac989876a5d3a82a267fdce1d" 326 | integrity sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ== 327 | 328 | easy-rbac@^3.2.0: 329 | version "3.2.0" 330 | resolved "https://registry.yarnpkg.com/easy-rbac/-/easy-rbac-3.2.0.tgz#bceeaebb491bf60dfd3824865e8a816c94697912" 331 | integrity sha512-cRBiDCWPg0QwTwR98lDFkar0ZpDXk7MWGxhqvnkXcPDPu0jtq82uRroxErgr3O78hgqWZ6BGcqYJRyyHD5Hl+A== 332 | dependencies: 333 | debug "^4.3.3" 334 | 335 | ecdsa-sig-formatter@1.0.11: 336 | version "1.0.11" 337 | resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" 338 | integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== 339 | dependencies: 340 | safe-buffer "^5.0.1" 341 | 342 | ee-first@1.1.1: 343 | version "1.1.1" 344 | resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" 345 | integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== 346 | 347 | emoji-regex@^8.0.0: 348 | version "8.0.0" 349 | resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" 350 | integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== 351 | 352 | encodeurl@~1.0.2: 353 | version "1.0.2" 354 | resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" 355 | integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== 356 | 357 | escape-html@~1.0.3: 358 | version "1.0.3" 359 | resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" 360 | integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== 361 | 362 | etag@~1.8.1: 363 | version "1.8.1" 364 | resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" 365 | integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== 366 | 367 | express@^4.17.3: 368 | version "4.18.1" 369 | resolved "https://registry.yarnpkg.com/express/-/express-4.18.1.tgz#7797de8b9c72c857b9cd0e14a5eea80666267caf" 370 | integrity sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q== 371 | dependencies: 372 | accepts "~1.3.8" 373 | array-flatten "1.1.1" 374 | body-parser "1.20.0" 375 | content-disposition "0.5.4" 376 | content-type "~1.0.4" 377 | cookie "0.5.0" 378 | cookie-signature "1.0.6" 379 | debug "2.6.9" 380 | depd "2.0.0" 381 | encodeurl "~1.0.2" 382 | escape-html "~1.0.3" 383 | etag "~1.8.1" 384 | finalhandler "1.2.0" 385 | fresh "0.5.2" 386 | http-errors "2.0.0" 387 | merge-descriptors "1.0.1" 388 | methods "~1.1.2" 389 | on-finished "2.4.1" 390 | parseurl "~1.3.3" 391 | path-to-regexp "0.1.7" 392 | proxy-addr "~2.0.7" 393 | qs "6.10.3" 394 | range-parser "~1.2.1" 395 | safe-buffer "5.2.1" 396 | send "0.18.0" 397 | serve-static "1.15.0" 398 | setprototypeof "1.2.0" 399 | statuses "2.0.1" 400 | type-is "~1.6.18" 401 | utils-merge "1.0.1" 402 | vary "~1.1.2" 403 | 404 | fill-range@^7.0.1: 405 | version "7.0.1" 406 | resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" 407 | integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== 408 | dependencies: 409 | to-regex-range "^5.0.1" 410 | 411 | finalhandler@1.2.0: 412 | version "1.2.0" 413 | resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" 414 | integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== 415 | dependencies: 416 | debug "2.6.9" 417 | encodeurl "~1.0.2" 418 | escape-html "~1.0.3" 419 | on-finished "2.4.1" 420 | parseurl "~1.3.3" 421 | statuses "2.0.1" 422 | unpipe "~1.0.0" 423 | 424 | forwarded@0.2.0: 425 | version "0.2.0" 426 | resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" 427 | integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== 428 | 429 | fresh@0.5.2: 430 | version "0.5.2" 431 | resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" 432 | integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== 433 | 434 | fs-minipass@^2.0.0: 435 | version "2.1.0" 436 | resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" 437 | integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== 438 | dependencies: 439 | minipass "^3.0.0" 440 | 441 | fs.realpath@^1.0.0: 442 | version "1.0.0" 443 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 444 | integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== 445 | 446 | fsevents@~2.3.2: 447 | version "2.3.2" 448 | resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" 449 | integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== 450 | 451 | function-bind@^1.1.1: 452 | version "1.1.1" 453 | resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" 454 | integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== 455 | 456 | gauge@^3.0.0: 457 | version "3.0.2" 458 | resolved "https://registry.yarnpkg.com/gauge/-/gauge-3.0.2.tgz#03bf4441c044383908bcfa0656ad91803259b395" 459 | integrity sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q== 460 | dependencies: 461 | aproba "^1.0.3 || ^2.0.0" 462 | color-support "^1.1.2" 463 | console-control-strings "^1.0.0" 464 | has-unicode "^2.0.1" 465 | object-assign "^4.1.1" 466 | signal-exit "^3.0.0" 467 | string-width "^4.2.3" 468 | strip-ansi "^6.0.1" 469 | wide-align "^1.1.2" 470 | 471 | get-intrinsic@^1.0.2: 472 | version "1.1.2" 473 | resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.2.tgz#336975123e05ad0b7ba41f152ee4aadbea6cf598" 474 | integrity sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA== 475 | dependencies: 476 | function-bind "^1.1.1" 477 | has "^1.0.3" 478 | has-symbols "^1.0.3" 479 | 480 | glob-parent@~5.1.2: 481 | version "5.1.2" 482 | resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" 483 | integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== 484 | dependencies: 485 | is-glob "^4.0.1" 486 | 487 | glob@^7.1.3: 488 | version "7.2.3" 489 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" 490 | integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== 491 | dependencies: 492 | fs.realpath "^1.0.0" 493 | inflight "^1.0.4" 494 | inherits "2" 495 | minimatch "^3.1.1" 496 | once "^1.3.0" 497 | path-is-absolute "^1.0.0" 498 | 499 | has-flag@^3.0.0: 500 | version "3.0.0" 501 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" 502 | integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== 503 | 504 | has-symbols@^1.0.3: 505 | version "1.0.3" 506 | resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" 507 | integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== 508 | 509 | has-unicode@^2.0.1: 510 | version "2.0.1" 511 | resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" 512 | integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== 513 | 514 | has@^1.0.3: 515 | version "1.0.3" 516 | resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" 517 | integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== 518 | dependencies: 519 | function-bind "^1.1.1" 520 | 521 | http-errors@2.0.0: 522 | version "2.0.0" 523 | resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" 524 | integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== 525 | dependencies: 526 | depd "2.0.0" 527 | inherits "2.0.4" 528 | setprototypeof "1.2.0" 529 | statuses "2.0.1" 530 | toidentifier "1.0.1" 531 | 532 | http-status-codes@^2.2.0: 533 | version "2.2.0" 534 | resolved "https://registry.yarnpkg.com/http-status-codes/-/http-status-codes-2.2.0.tgz#bb2efe63d941dfc2be18e15f703da525169622be" 535 | integrity sha512-feERVo9iWxvnejp3SEfm/+oNG517npqL2/PIA8ORjyOZjGC7TwCRQsZylciLS64i6pJ0wRYz3rkXLRwbtFa8Ng== 536 | 537 | https-proxy-agent@^5.0.0: 538 | version "5.0.1" 539 | resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" 540 | integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== 541 | dependencies: 542 | agent-base "6" 543 | debug "4" 544 | 545 | iconv-lite@0.4.24: 546 | version "0.4.24" 547 | resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" 548 | integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== 549 | dependencies: 550 | safer-buffer ">= 2.1.2 < 3" 551 | 552 | ieee754@^1.1.13: 553 | version "1.2.1" 554 | resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" 555 | integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== 556 | 557 | ignore-by-default@^1.0.1: 558 | version "1.0.1" 559 | resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" 560 | integrity sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA== 561 | 562 | inflight@^1.0.4: 563 | version "1.0.6" 564 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 565 | integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== 566 | dependencies: 567 | once "^1.3.0" 568 | wrappy "1" 569 | 570 | inherits@2, inherits@2.0.4, inherits@^2.0.3: 571 | version "2.0.4" 572 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 573 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 574 | 575 | ip@^2.0.0: 576 | version "2.0.0" 577 | resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.0.tgz#4cf4ab182fee2314c75ede1276f8c80b479936da" 578 | integrity sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ== 579 | 580 | ipaddr.js@1.9.1: 581 | version "1.9.1" 582 | resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" 583 | integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== 584 | 585 | is-binary-path@~2.1.0: 586 | version "2.1.0" 587 | resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" 588 | integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== 589 | dependencies: 590 | binary-extensions "^2.0.0" 591 | 592 | is-extglob@^2.1.1: 593 | version "2.1.1" 594 | resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" 595 | integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== 596 | 597 | is-fullwidth-code-point@^3.0.0: 598 | version "3.0.0" 599 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" 600 | integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== 601 | 602 | is-glob@^4.0.1, is-glob@~4.0.1: 603 | version "4.0.3" 604 | resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" 605 | integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== 606 | dependencies: 607 | is-extglob "^2.1.1" 608 | 609 | is-number@^7.0.0: 610 | version "7.0.0" 611 | resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" 612 | integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== 613 | 614 | joi@^17.6.0: 615 | version "17.6.0" 616 | resolved "https://registry.yarnpkg.com/joi/-/joi-17.6.0.tgz#0bb54f2f006c09a96e75ce687957bd04290054b2" 617 | integrity sha512-OX5dG6DTbcr/kbMFj0KGYxuew69HPcAE3K/sZpEV2nP6e/j/C0HV+HNiBPCASxdx5T7DMoa0s8UeHWMnb6n2zw== 618 | dependencies: 619 | "@hapi/hoek" "^9.0.0" 620 | "@hapi/topo" "^5.0.0" 621 | "@sideway/address" "^4.1.3" 622 | "@sideway/formula" "^3.0.0" 623 | "@sideway/pinpoint" "^2.0.0" 624 | 625 | jsonwebtoken@^8.5.1: 626 | version "8.5.1" 627 | resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d" 628 | integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w== 629 | dependencies: 630 | jws "^3.2.2" 631 | lodash.includes "^4.3.0" 632 | lodash.isboolean "^3.0.3" 633 | lodash.isinteger "^4.0.4" 634 | lodash.isnumber "^3.0.3" 635 | lodash.isplainobject "^4.0.6" 636 | lodash.isstring "^4.0.1" 637 | lodash.once "^4.0.0" 638 | ms "^2.1.1" 639 | semver "^5.6.0" 640 | 641 | jwa@^1.4.1: 642 | version "1.4.1" 643 | resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" 644 | integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== 645 | dependencies: 646 | buffer-equal-constant-time "1.0.1" 647 | ecdsa-sig-formatter "1.0.11" 648 | safe-buffer "^5.0.1" 649 | 650 | jws@^3.2.2: 651 | version "3.2.2" 652 | resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" 653 | integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== 654 | dependencies: 655 | jwa "^1.4.1" 656 | safe-buffer "^5.0.1" 657 | 658 | kareem@2.4.1: 659 | version "2.4.1" 660 | resolved "https://registry.yarnpkg.com/kareem/-/kareem-2.4.1.tgz#7d81ec518204a48c1cb16554af126806c3cd82b0" 661 | integrity sha512-aJ9opVoXroQUPfovYP5kaj2lM7Jn02Gw13bL0lg9v0V7SaUc0qavPs0Eue7d2DcC3NjqI6QAUElXNsuZSeM+EA== 662 | 663 | keymirror@^0.1.1: 664 | version "0.1.1" 665 | resolved "https://registry.yarnpkg.com/keymirror/-/keymirror-0.1.1.tgz#918889ea13f8d0a42e7c557250eee713adc95c35" 666 | integrity sha512-vIkZAFWoDijgQT/Nvl2AHCMmnegN2ehgTPYuyy2hWQkQSntI0S7ESYqdLkoSe1HyEBFHHkCgSIvVdSEiWwKvCg== 667 | 668 | lodash.includes@^4.3.0: 669 | version "4.3.0" 670 | resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" 671 | integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w== 672 | 673 | lodash.isboolean@^3.0.3: 674 | version "3.0.3" 675 | resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" 676 | integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg== 677 | 678 | lodash.isinteger@^4.0.4: 679 | version "4.0.4" 680 | resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" 681 | integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA== 682 | 683 | lodash.isnumber@^3.0.3: 684 | version "3.0.3" 685 | resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" 686 | integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw== 687 | 688 | lodash.isplainobject@^4.0.6: 689 | version "4.0.6" 690 | resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" 691 | integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== 692 | 693 | lodash.isstring@^4.0.1: 694 | version "4.0.1" 695 | resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" 696 | integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw== 697 | 698 | lodash.once@^4.0.0: 699 | version "4.1.1" 700 | resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" 701 | integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== 702 | 703 | lodash@^4.17.10: 704 | version "4.17.21" 705 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" 706 | integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== 707 | 708 | lru-cache@^6.0.0: 709 | version "6.0.0" 710 | resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" 711 | integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== 712 | dependencies: 713 | yallist "^4.0.0" 714 | 715 | make-dir@^3.1.0: 716 | version "3.1.0" 717 | resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" 718 | integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== 719 | dependencies: 720 | semver "^6.0.0" 721 | 722 | media-typer@0.3.0: 723 | version "0.3.0" 724 | resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" 725 | integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== 726 | 727 | memory-pager@^1.0.2: 728 | version "1.5.0" 729 | resolved "https://registry.yarnpkg.com/memory-pager/-/memory-pager-1.5.0.tgz#d8751655d22d384682741c972f2c3d6dfa3e66b5" 730 | integrity sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg== 731 | 732 | merge-descriptors@1.0.1: 733 | version "1.0.1" 734 | resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" 735 | integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== 736 | 737 | methods@~1.1.2: 738 | version "1.1.2" 739 | resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" 740 | integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== 741 | 742 | mime-db@1.52.0: 743 | version "1.52.0" 744 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" 745 | integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== 746 | 747 | mime-types@~2.1.24, mime-types@~2.1.34: 748 | version "2.1.35" 749 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" 750 | integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== 751 | dependencies: 752 | mime-db "1.52.0" 753 | 754 | mime@1.6.0: 755 | version "1.6.0" 756 | resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" 757 | integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== 758 | 759 | minimatch@^3.0.4, minimatch@^3.1.1: 760 | version "3.1.2" 761 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" 762 | integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== 763 | dependencies: 764 | brace-expansion "^1.1.7" 765 | 766 | minipass@^3.0.0: 767 | version "3.3.4" 768 | resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.4.tgz#ca99f95dd77c43c7a76bf51e6d200025eee0ffae" 769 | integrity sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw== 770 | dependencies: 771 | yallist "^4.0.0" 772 | 773 | minizlib@^2.1.1: 774 | version "2.1.2" 775 | resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" 776 | integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== 777 | dependencies: 778 | minipass "^3.0.0" 779 | yallist "^4.0.0" 780 | 781 | mkdirp@^1.0.3: 782 | version "1.0.4" 783 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" 784 | integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== 785 | 786 | mongodb-connection-string-url@^2.5.2: 787 | version "2.5.3" 788 | resolved "https://registry.yarnpkg.com/mongodb-connection-string-url/-/mongodb-connection-string-url-2.5.3.tgz#c0c572b71570e58be2bd52b33dffd1330cfb6990" 789 | integrity sha512-f+/WsED+xF4B74l3k9V/XkTVj5/fxFH2o5ToKXd8Iyi5UhM+sO9u0Ape17Mvl/GkZaFtM0HQnzAG5OTmhKw+tQ== 790 | dependencies: 791 | "@types/whatwg-url" "^8.2.1" 792 | whatwg-url "^11.0.0" 793 | 794 | mongodb@4.8.1: 795 | version "4.8.1" 796 | resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-4.8.1.tgz#596de88ff4519128266d9254dbe5b781c4005796" 797 | integrity sha512-/NyiM3Ox9AwP5zrfT9TXjRKDJbXlLaUDQ9Rg//2lbg8D2A8GXV0VidYYnA/gfdK6uwbnL4FnAflH7FbGw3TS7w== 798 | dependencies: 799 | bson "^4.6.5" 800 | denque "^2.0.1" 801 | mongodb-connection-string-url "^2.5.2" 802 | socks "^2.6.2" 803 | optionalDependencies: 804 | saslprep "^1.0.3" 805 | 806 | mongoose@^6.3.0: 807 | version "6.5.2" 808 | resolved "https://registry.yarnpkg.com/mongoose/-/mongoose-6.5.2.tgz#630ae67c4d2576635ba1f859b2840560b27b7775" 809 | integrity sha512-3CFDrSLtK2qjM1pZeZpLTUyqPRkc11Iuh74ZrwS4IwEJ3K2PqGnmyPLw7ex4Kzu37ujIMp3MAuiBlUjfrcb6hw== 810 | dependencies: 811 | bson "^4.6.5" 812 | kareem "2.4.1" 813 | mongodb "4.8.1" 814 | mpath "0.9.0" 815 | mquery "4.0.3" 816 | ms "2.1.3" 817 | sift "16.0.0" 818 | 819 | mpath@0.9.0: 820 | version "0.9.0" 821 | resolved "https://registry.yarnpkg.com/mpath/-/mpath-0.9.0.tgz#0c122fe107846e31fc58c75b09c35514b3871904" 822 | integrity sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew== 823 | 824 | mquery@4.0.3: 825 | version "4.0.3" 826 | resolved "https://registry.yarnpkg.com/mquery/-/mquery-4.0.3.tgz#4d15f938e6247d773a942c912d9748bd1965f89d" 827 | integrity sha512-J5heI+P08I6VJ2Ky3+33IpCdAvlYGTSUjwTPxkAr8i8EoduPMBX2OY/wa3IKZIQl7MU4SbFk8ndgSKyB/cl1zA== 828 | dependencies: 829 | debug "4.x" 830 | 831 | ms@2.0.0: 832 | version "2.0.0" 833 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" 834 | integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== 835 | 836 | ms@2.1.2: 837 | version "2.1.2" 838 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" 839 | integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== 840 | 841 | ms@2.1.3, ms@^2.1.1: 842 | version "2.1.3" 843 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" 844 | integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== 845 | 846 | negotiator@0.6.3: 847 | version "0.6.3" 848 | resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" 849 | integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== 850 | 851 | node-addon-api@^3.1.0: 852 | version "3.2.1" 853 | resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161" 854 | integrity sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A== 855 | 856 | node-fetch@^2.6.7: 857 | version "2.6.7" 858 | resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" 859 | integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== 860 | dependencies: 861 | whatwg-url "^5.0.0" 862 | 863 | nodemon@^2.0.15: 864 | version "2.0.19" 865 | resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.19.tgz#cac175f74b9cb8b57e770d47841995eebe4488bd" 866 | integrity sha512-4pv1f2bMDj0Eeg/MhGqxrtveeQ5/G/UVe9iO6uTZzjnRluSA4PVWf8CW99LUPwGB3eNIA7zUFoP77YuI7hOc0A== 867 | dependencies: 868 | chokidar "^3.5.2" 869 | debug "^3.2.7" 870 | ignore-by-default "^1.0.1" 871 | minimatch "^3.0.4" 872 | pstree.remy "^1.1.8" 873 | semver "^5.7.1" 874 | simple-update-notifier "^1.0.7" 875 | supports-color "^5.5.0" 876 | touch "^3.1.0" 877 | undefsafe "^2.0.5" 878 | 879 | nopt@^5.0.0: 880 | version "5.0.0" 881 | resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88" 882 | integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ== 883 | dependencies: 884 | abbrev "1" 885 | 886 | nopt@~1.0.10: 887 | version "1.0.10" 888 | resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" 889 | integrity sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg== 890 | dependencies: 891 | abbrev "1" 892 | 893 | normalize-path@^3.0.0, normalize-path@~3.0.0: 894 | version "3.0.0" 895 | resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" 896 | integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== 897 | 898 | npmlog@^5.0.1: 899 | version "5.0.1" 900 | resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-5.0.1.tgz#f06678e80e29419ad67ab964e0fa69959c1eb8b0" 901 | integrity sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw== 902 | dependencies: 903 | are-we-there-yet "^2.0.0" 904 | console-control-strings "^1.1.0" 905 | gauge "^3.0.0" 906 | set-blocking "^2.0.0" 907 | 908 | object-assign@^4, object-assign@^4.1.1: 909 | version "4.1.1" 910 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" 911 | integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== 912 | 913 | object-inspect@^1.9.0: 914 | version "1.12.2" 915 | resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea" 916 | integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ== 917 | 918 | on-finished@2.4.1: 919 | version "2.4.1" 920 | resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" 921 | integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== 922 | dependencies: 923 | ee-first "1.1.1" 924 | 925 | once@^1.3.0: 926 | version "1.4.0" 927 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 928 | integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== 929 | dependencies: 930 | wrappy "1" 931 | 932 | parseurl@~1.3.3: 933 | version "1.3.3" 934 | resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" 935 | integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== 936 | 937 | path-is-absolute@^1.0.0: 938 | version "1.0.1" 939 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 940 | integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== 941 | 942 | path-to-regexp@0.1.7: 943 | version "0.1.7" 944 | resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" 945 | integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== 946 | 947 | picomatch@^2.0.4, picomatch@^2.2.1: 948 | version "2.3.1" 949 | resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" 950 | integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== 951 | 952 | proxy-addr@~2.0.7: 953 | version "2.0.7" 954 | resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" 955 | integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== 956 | dependencies: 957 | forwarded "0.2.0" 958 | ipaddr.js "1.9.1" 959 | 960 | pstree.remy@^1.1.8: 961 | version "1.1.8" 962 | resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a" 963 | integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w== 964 | 965 | punycode@^2.1.1: 966 | version "2.1.1" 967 | resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" 968 | integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== 969 | 970 | qs@6.10.3: 971 | version "6.10.3" 972 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.3.tgz#d6cde1b2ffca87b5aa57889816c5f81535e22e8e" 973 | integrity sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ== 974 | dependencies: 975 | side-channel "^1.0.4" 976 | 977 | range-parser@~1.2.1: 978 | version "1.2.1" 979 | resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" 980 | integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== 981 | 982 | raw-body@2.5.1: 983 | version "2.5.1" 984 | resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857" 985 | integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig== 986 | dependencies: 987 | bytes "3.1.2" 988 | http-errors "2.0.0" 989 | iconv-lite "0.4.24" 990 | unpipe "1.0.0" 991 | 992 | rbac@^5.0.3: 993 | version "5.0.3" 994 | resolved "https://registry.yarnpkg.com/rbac/-/rbac-5.0.3.tgz#9dec3375e678484af1080cb9bf4257c4178362e8" 995 | integrity sha512-3kvvY5+kCy2dGnAYx6c0j8mqyYs/kdnwn8HI+dl/1vacEe142t4OUXMSeIPBPN26qH5b4m+FS1iT99GQ+vPRug== 996 | dependencies: 997 | keymirror "^0.1.1" 998 | lodash "^4.17.10" 999 | 1000 | readable-stream@^3.6.0: 1001 | version "3.6.0" 1002 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" 1003 | integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== 1004 | dependencies: 1005 | inherits "^2.0.3" 1006 | string_decoder "^1.1.1" 1007 | util-deprecate "^1.0.1" 1008 | 1009 | readdirp@~3.6.0: 1010 | version "3.6.0" 1011 | resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" 1012 | integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== 1013 | dependencies: 1014 | picomatch "^2.2.1" 1015 | 1016 | rimraf@^3.0.2: 1017 | version "3.0.2" 1018 | resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" 1019 | integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== 1020 | dependencies: 1021 | glob "^7.1.3" 1022 | 1023 | safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@~5.2.0: 1024 | version "5.2.1" 1025 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" 1026 | integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== 1027 | 1028 | "safer-buffer@>= 2.1.2 < 3": 1029 | version "2.1.2" 1030 | resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" 1031 | integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== 1032 | 1033 | saslprep@^1.0.3: 1034 | version "1.0.3" 1035 | resolved "https://registry.yarnpkg.com/saslprep/-/saslprep-1.0.3.tgz#4c02f946b56cf54297e347ba1093e7acac4cf226" 1036 | integrity sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag== 1037 | dependencies: 1038 | sparse-bitfield "^3.0.3" 1039 | 1040 | semver@^5.6.0, semver@^5.7.1: 1041 | version "5.7.1" 1042 | resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" 1043 | integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== 1044 | 1045 | semver@^6.0.0: 1046 | version "6.3.0" 1047 | resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" 1048 | integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== 1049 | 1050 | semver@^7.3.5: 1051 | version "7.3.7" 1052 | resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" 1053 | integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== 1054 | dependencies: 1055 | lru-cache "^6.0.0" 1056 | 1057 | semver@~7.0.0: 1058 | version "7.0.0" 1059 | resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" 1060 | integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== 1061 | 1062 | send@0.18.0: 1063 | version "0.18.0" 1064 | resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" 1065 | integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== 1066 | dependencies: 1067 | debug "2.6.9" 1068 | depd "2.0.0" 1069 | destroy "1.2.0" 1070 | encodeurl "~1.0.2" 1071 | escape-html "~1.0.3" 1072 | etag "~1.8.1" 1073 | fresh "0.5.2" 1074 | http-errors "2.0.0" 1075 | mime "1.6.0" 1076 | ms "2.1.3" 1077 | on-finished "2.4.1" 1078 | range-parser "~1.2.1" 1079 | statuses "2.0.1" 1080 | 1081 | serve-static@1.15.0: 1082 | version "1.15.0" 1083 | resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" 1084 | integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== 1085 | dependencies: 1086 | encodeurl "~1.0.2" 1087 | escape-html "~1.0.3" 1088 | parseurl "~1.3.3" 1089 | send "0.18.0" 1090 | 1091 | set-blocking@^2.0.0: 1092 | version "2.0.0" 1093 | resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" 1094 | integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== 1095 | 1096 | setprototypeof@1.2.0: 1097 | version "1.2.0" 1098 | resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" 1099 | integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== 1100 | 1101 | side-channel@^1.0.4: 1102 | version "1.0.4" 1103 | resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" 1104 | integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== 1105 | dependencies: 1106 | call-bind "^1.0.0" 1107 | get-intrinsic "^1.0.2" 1108 | object-inspect "^1.9.0" 1109 | 1110 | sift@16.0.0: 1111 | version "16.0.0" 1112 | resolved "https://registry.yarnpkg.com/sift/-/sift-16.0.0.tgz#447991577db61f1a8fab727a8a98a6db57a23eb8" 1113 | integrity sha512-ILTjdP2Mv9V1kIxWMXeMTIRbOBrqKc4JAXmFMnFq3fKeyQ2Qwa3Dw1ubcye3vR+Y6ofA0b9gNDr/y2t6eUeIzQ== 1114 | 1115 | signal-exit@^3.0.0: 1116 | version "3.0.7" 1117 | resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" 1118 | integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== 1119 | 1120 | simple-update-notifier@^1.0.7: 1121 | version "1.0.7" 1122 | resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-1.0.7.tgz#7edf75c5bdd04f88828d632f762b2bc32996a9cc" 1123 | integrity sha512-BBKgR84BJQJm6WjWFMHgLVuo61FBDSj1z/xSFUIozqO6wO7ii0JxCqlIud7Enr/+LhlbNI0whErq96P2qHNWew== 1124 | dependencies: 1125 | semver "~7.0.0" 1126 | 1127 | smart-buffer@^4.2.0: 1128 | version "4.2.0" 1129 | resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" 1130 | integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== 1131 | 1132 | socks@^2.6.2: 1133 | version "2.7.0" 1134 | resolved "https://registry.yarnpkg.com/socks/-/socks-2.7.0.tgz#f9225acdb841e874dca25f870e9130990f3913d0" 1135 | integrity sha512-scnOe9y4VuiNUULJN72GrM26BNOjVsfPXI+j+98PkyEfsIXroa5ofyjT+FzGvn/xHs73U2JtoBYAVx9Hl4quSA== 1136 | dependencies: 1137 | ip "^2.0.0" 1138 | smart-buffer "^4.2.0" 1139 | 1140 | sparse-bitfield@^3.0.3: 1141 | version "3.0.3" 1142 | resolved "https://registry.yarnpkg.com/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz#ff4ae6e68656056ba4b3e792ab3334d38273ca11" 1143 | integrity sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ== 1144 | dependencies: 1145 | memory-pager "^1.0.2" 1146 | 1147 | statuses@2.0.1: 1148 | version "2.0.1" 1149 | resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" 1150 | integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== 1151 | 1152 | "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.2.3: 1153 | version "4.2.3" 1154 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" 1155 | integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== 1156 | dependencies: 1157 | emoji-regex "^8.0.0" 1158 | is-fullwidth-code-point "^3.0.0" 1159 | strip-ansi "^6.0.1" 1160 | 1161 | string_decoder@^1.1.1: 1162 | version "1.3.0" 1163 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" 1164 | integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== 1165 | dependencies: 1166 | safe-buffer "~5.2.0" 1167 | 1168 | strip-ansi@^6.0.1: 1169 | version "6.0.1" 1170 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" 1171 | integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== 1172 | dependencies: 1173 | ansi-regex "^5.0.1" 1174 | 1175 | supports-color@^5.5.0: 1176 | version "5.5.0" 1177 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" 1178 | integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== 1179 | dependencies: 1180 | has-flag "^3.0.0" 1181 | 1182 | tar@^6.1.11: 1183 | version "6.1.11" 1184 | resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621" 1185 | integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA== 1186 | dependencies: 1187 | chownr "^2.0.0" 1188 | fs-minipass "^2.0.0" 1189 | minipass "^3.0.0" 1190 | minizlib "^2.1.1" 1191 | mkdirp "^1.0.3" 1192 | yallist "^4.0.0" 1193 | 1194 | to-regex-range@^5.0.1: 1195 | version "5.0.1" 1196 | resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" 1197 | integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== 1198 | dependencies: 1199 | is-number "^7.0.0" 1200 | 1201 | toidentifier@1.0.1: 1202 | version "1.0.1" 1203 | resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" 1204 | integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== 1205 | 1206 | touch@^3.1.0: 1207 | version "3.1.0" 1208 | resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b" 1209 | integrity sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA== 1210 | dependencies: 1211 | nopt "~1.0.10" 1212 | 1213 | tr46@^3.0.0: 1214 | version "3.0.0" 1215 | resolved "https://registry.yarnpkg.com/tr46/-/tr46-3.0.0.tgz#555c4e297a950617e8eeddef633c87d4d9d6cbf9" 1216 | integrity sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA== 1217 | dependencies: 1218 | punycode "^2.1.1" 1219 | 1220 | tr46@~0.0.3: 1221 | version "0.0.3" 1222 | resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" 1223 | integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== 1224 | 1225 | type-is@~1.6.18: 1226 | version "1.6.18" 1227 | resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" 1228 | integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== 1229 | dependencies: 1230 | media-typer "0.3.0" 1231 | mime-types "~2.1.24" 1232 | 1233 | undefsafe@^2.0.5: 1234 | version "2.0.5" 1235 | resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" 1236 | integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== 1237 | 1238 | unpipe@1.0.0, unpipe@~1.0.0: 1239 | version "1.0.0" 1240 | resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" 1241 | integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== 1242 | 1243 | util-deprecate@^1.0.1: 1244 | version "1.0.2" 1245 | resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" 1246 | integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== 1247 | 1248 | utils-merge@1.0.1: 1249 | version "1.0.1" 1250 | resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" 1251 | integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== 1252 | 1253 | vary@^1, vary@~1.1.2: 1254 | version "1.1.2" 1255 | resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" 1256 | integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== 1257 | 1258 | webidl-conversions@^3.0.0: 1259 | version "3.0.1" 1260 | resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" 1261 | integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== 1262 | 1263 | webidl-conversions@^7.0.0: 1264 | version "7.0.0" 1265 | resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" 1266 | integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g== 1267 | 1268 | whatwg-url@^11.0.0: 1269 | version "11.0.0" 1270 | resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-11.0.0.tgz#0a849eebb5faf2119b901bb76fd795c2848d4018" 1271 | integrity sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ== 1272 | dependencies: 1273 | tr46 "^3.0.0" 1274 | webidl-conversions "^7.0.0" 1275 | 1276 | whatwg-url@^5.0.0: 1277 | version "5.0.0" 1278 | resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" 1279 | integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== 1280 | dependencies: 1281 | tr46 "~0.0.3" 1282 | webidl-conversions "^3.0.0" 1283 | 1284 | wide-align@^1.1.2: 1285 | version "1.1.5" 1286 | resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" 1287 | integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== 1288 | dependencies: 1289 | string-width "^1.0.2 || 2 || 3 || 4" 1290 | 1291 | wrappy@1: 1292 | version "1.0.2" 1293 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 1294 | integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== 1295 | 1296 | yallist@^4.0.0: 1297 | version "4.0.0" 1298 | resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" 1299 | integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== 1300 | --------------------------------------------------------------------------------