├── .gitignore ├── .idea ├── .gitignore ├── modules.xml └── rtk-query.iml ├── .prettierignore ├── .prettierrc ├── README.md ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt ├── server ├── data │ └── example.json ├── package-lock.json ├── package.json ├── src │ ├── helpers │ │ └── server.helper.ts │ ├── models │ │ └── example.model.ts │ ├── routers │ │ ├── example.router.ts │ │ └── index.ts │ └── server.ts └── tsconfig.json ├── src ├── components │ ├── app │ │ └── app.container.tsx │ └── example │ │ ├── example.component.tsx │ │ ├── example.module.css │ │ └── list │ │ ├── example-list.container.tsx │ │ ├── example-list.hook.ts │ │ └── example-list.module.css ├── constants │ └── api.constants.ts ├── index.css ├── index.tsx ├── logo.svg ├── models │ └── example.model.ts ├── react-app-env.d.ts ├── reportWebVitals.ts ├── setupTests.ts └── store │ ├── common.api.ts │ ├── example │ └── example.api.ts │ └── store.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | /.pnp 6 | .pnp.js 7 | .idea 8 | 9 | # testing 10 | /coverage 11 | 12 | # production 13 | build 14 | dist 15 | 16 | # misc 17 | .DS_Store 18 | .env.local 19 | .env.development.local 20 | .env.test.local 21 | .env.production.local 22 | 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/rtk-query.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .idea/* 2 | dist 3 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 4, 3 | "semi": true, 4 | "arrowParens": "avoid", 5 | "singleQuote": true, 6 | "jsxSingleQuote": true, 7 | "printWidth": 120, 8 | "endOfLine": "auto" 9 | } 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RTK query example 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 | ### server 10 | `npm start` in server folder 11 | 12 | Runs express server app in the development mode on 3003 port. 13 | 14 | ### `npm start` 15 | 16 | Runs the app in the development mode.\ 17 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 18 | 19 | The page will reload if you make edits.\ 20 | You will also see any lint errors in the console. 21 | 22 | ### `npm test` 23 | 24 | Launches the test runner in the interactive watch mode.\ 25 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 26 | 27 | ### `npm run build` 28 | 29 | Builds the app for production to the `build` folder.\ 30 | It correctly bundles React in production mode and optimizes the build for the best performance. 31 | 32 | The build is minified and the filenames include the hashes.\ 33 | Your app is ready to be deployed! 34 | 35 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 36 | 37 | ### `npm run eject` 38 | 39 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 40 | 41 | 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. 42 | 43 | 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. 44 | 45 | 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. 46 | 47 | ## Learn More 48 | 49 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 50 | 51 | To learn React, check out the [React documentation](https://reactjs.org/). 52 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rtk-query", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@reduxjs/toolkit": "^1.6.2", 7 | "@testing-library/jest-dom": "^5.15.1", 8 | "@testing-library/react": "^12.1.2", 9 | "@testing-library/user-event": "^13.5.0", 10 | "@types/jest": "^27.0.3", 11 | "@types/node": "^16.11.11", 12 | "@types/react": "^17.0.37", 13 | "@types/react-dom": "^17.0.11", 14 | "react": "^17.0.2", 15 | "react-dom": "^17.0.2", 16 | "react-redux": "^7.2.6", 17 | "react-scripts": "5.0.0-next.47", 18 | "typescript": "^4.5.2", 19 | "web-vitals": "^2.1.2" 20 | }, 21 | "devDependencies": { 22 | "prettier": "2.5.0" 23 | }, 24 | "scripts": { 25 | "start": "react-scripts start", 26 | "build": "react-scripts build", 27 | "test": "react-scripts test", 28 | "eject": "react-scripts eject" 29 | }, 30 | "eslintConfig": { 31 | "extends": [ 32 | "react-app", 33 | "react-app/jest" 34 | ] 35 | }, 36 | "browserslist": { 37 | "production": [ 38 | ">0.2%", 39 | "not dead", 40 | "not op_mini all" 41 | ], 42 | "development": [ 43 | "last 1 chrome version", 44 | "last 1 firefox version", 45 | "last 1 safari version" 46 | ] 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxim1006/RTK-query/ebbe77cbaa79e111bb394aabf862bec9b5e872ca/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxim1006/RTK-query/ebbe77cbaa79e111bb394aabf862bec9b5e872ca/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxim1006/RTK-query/ebbe77cbaa79e111bb394aabf862bec9b5e872ca/public/logo512.png -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /server/data/example.json: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /server/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/body-parser": { 8 | "version": "1.19.2", 9 | "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", 10 | "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", 11 | "requires": { 12 | "@types/connect": "*", 13 | "@types/node": "*" 14 | } 15 | }, 16 | "@types/compression": { 17 | "version": "1.7.2", 18 | "resolved": "https://registry.npmjs.org/@types/compression/-/compression-1.7.2.tgz", 19 | "integrity": "sha512-lwEL4M/uAGWngWFLSG87ZDr2kLrbuR8p7X+QZB1OQlT+qkHsCPDVFnHPyXf4Vyl4yDDorNY+mAhosxkCvppatg==", 20 | "requires": { 21 | "@types/express": "*" 22 | } 23 | }, 24 | "@types/connect": { 25 | "version": "3.4.35", 26 | "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", 27 | "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", 28 | "requires": { 29 | "@types/node": "*" 30 | } 31 | }, 32 | "@types/cors": { 33 | "version": "2.8.12", 34 | "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", 35 | "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==" 36 | }, 37 | "@types/express": { 38 | "version": "4.17.13", 39 | "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", 40 | "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", 41 | "requires": { 42 | "@types/body-parser": "*", 43 | "@types/express-serve-static-core": "^4.17.18", 44 | "@types/qs": "*", 45 | "@types/serve-static": "*" 46 | } 47 | }, 48 | "@types/express-serve-static-core": { 49 | "version": "4.17.25", 50 | "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.25.tgz", 51 | "integrity": "sha512-OUJIVfRMFijZukGGwTpKNFprqCCXk5WjNGvUgB/CxxBR40QWSjsNK86+yvGKlCOGc7sbwfHLaXhkG+NsytwBaQ==", 52 | "requires": { 53 | "@types/node": "*", 54 | "@types/qs": "*", 55 | "@types/range-parser": "*" 56 | } 57 | }, 58 | "@types/fs-extra": { 59 | "version": "9.0.13", 60 | "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", 61 | "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==", 62 | "dev": true, 63 | "requires": { 64 | "@types/node": "*" 65 | } 66 | }, 67 | "@types/mime": { 68 | "version": "1.3.2", 69 | "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", 70 | "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" 71 | }, 72 | "@types/node": { 73 | "version": "16.11.10", 74 | "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.10.tgz", 75 | "integrity": "sha512-3aRnHa1KlOEEhJ6+CvyHKK5vE9BcLGjtUpwvqYLRvYNQKMfabu3BwfJaA/SLW8dxe28LsNDjtHwePTuzn3gmOA==" 76 | }, 77 | "@types/qs": { 78 | "version": "6.9.7", 79 | "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", 80 | "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" 81 | }, 82 | "@types/range-parser": { 83 | "version": "1.2.4", 84 | "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", 85 | "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" 86 | }, 87 | "@types/serve-static": { 88 | "version": "1.13.10", 89 | "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz", 90 | "integrity": "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==", 91 | "requires": { 92 | "@types/mime": "^1", 93 | "@types/node": "*" 94 | } 95 | }, 96 | "@types/uuid": { 97 | "version": "8.3.3", 98 | "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.3.tgz", 99 | "integrity": "sha512-0LbEEx1zxrYB3pgpd1M5lEhLcXjKJnYghvhTRgaBeUivLHMDM1TzF3IJ6hXU2+8uA4Xz+5BA63mtZo5DjVT8iA==", 100 | "dev": true 101 | }, 102 | "accepts": { 103 | "version": "1.3.7", 104 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", 105 | "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", 106 | "dev": true, 107 | "requires": { 108 | "mime-types": "~2.1.24", 109 | "negotiator": "0.6.2" 110 | } 111 | }, 112 | "ansi-regex": { 113 | "version": "5.0.1", 114 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 115 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 116 | "dev": true 117 | }, 118 | "array-flatten": { 119 | "version": "1.1.1", 120 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 121 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", 122 | "dev": true 123 | }, 124 | "body-parser": { 125 | "version": "1.19.0", 126 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", 127 | "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", 128 | "dev": true, 129 | "requires": { 130 | "bytes": "3.1.0", 131 | "content-type": "~1.0.4", 132 | "debug": "2.6.9", 133 | "depd": "~1.1.2", 134 | "http-errors": "1.7.2", 135 | "iconv-lite": "0.4.24", 136 | "on-finished": "~2.3.0", 137 | "qs": "6.7.0", 138 | "raw-body": "2.4.0", 139 | "type-is": "~1.6.17" 140 | }, 141 | "dependencies": { 142 | "bytes": { 143 | "version": "3.1.0", 144 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", 145 | "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", 146 | "dev": true 147 | } 148 | } 149 | }, 150 | "bytes": { 151 | "version": "3.0.0", 152 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", 153 | "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", 154 | "dev": true 155 | }, 156 | "compressible": { 157 | "version": "2.0.18", 158 | "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", 159 | "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", 160 | "dev": true, 161 | "requires": { 162 | "mime-db": ">= 1.43.0 < 2" 163 | } 164 | }, 165 | "compression": { 166 | "version": "1.7.4", 167 | "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", 168 | "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", 169 | "dev": true, 170 | "requires": { 171 | "accepts": "~1.3.5", 172 | "bytes": "3.0.0", 173 | "compressible": "~2.0.16", 174 | "debug": "2.6.9", 175 | "on-headers": "~1.0.2", 176 | "safe-buffer": "5.1.2", 177 | "vary": "~1.1.2" 178 | } 179 | }, 180 | "content-disposition": { 181 | "version": "0.5.3", 182 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", 183 | "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", 184 | "dev": true, 185 | "requires": { 186 | "safe-buffer": "5.1.2" 187 | } 188 | }, 189 | "content-type": { 190 | "version": "1.0.4", 191 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 192 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", 193 | "dev": true 194 | }, 195 | "cookie": { 196 | "version": "0.4.0", 197 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", 198 | "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", 199 | "dev": true 200 | }, 201 | "cookie-signature": { 202 | "version": "1.0.6", 203 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 204 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", 205 | "dev": true 206 | }, 207 | "cors": { 208 | "version": "2.8.5", 209 | "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", 210 | "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", 211 | "dev": true, 212 | "requires": { 213 | "object-assign": "^4", 214 | "vary": "^1" 215 | } 216 | }, 217 | "cross-spawn": { 218 | "version": "7.0.3", 219 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", 220 | "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", 221 | "dev": true, 222 | "requires": { 223 | "path-key": "^3.1.0", 224 | "shebang-command": "^2.0.0", 225 | "which": "^2.0.1" 226 | } 227 | }, 228 | "debug": { 229 | "version": "2.6.9", 230 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 231 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 232 | "dev": true, 233 | "requires": { 234 | "ms": "2.0.0" 235 | } 236 | }, 237 | "depd": { 238 | "version": "1.1.2", 239 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 240 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", 241 | "dev": true 242 | }, 243 | "destroy": { 244 | "version": "1.0.4", 245 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 246 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", 247 | "dev": true 248 | }, 249 | "dotenv": { 250 | "version": "10.0.0", 251 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", 252 | "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", 253 | "dev": true 254 | }, 255 | "duplexer": { 256 | "version": "0.1.2", 257 | "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", 258 | "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", 259 | "dev": true 260 | }, 261 | "ee-first": { 262 | "version": "1.1.1", 263 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 264 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", 265 | "dev": true 266 | }, 267 | "encodeurl": { 268 | "version": "1.0.2", 269 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 270 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", 271 | "dev": true 272 | }, 273 | "escape-html": { 274 | "version": "1.0.3", 275 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 276 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", 277 | "dev": true 278 | }, 279 | "etag": { 280 | "version": "1.8.1", 281 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 282 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", 283 | "dev": true 284 | }, 285 | "event-stream": { 286 | "version": "3.3.4", 287 | "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", 288 | "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", 289 | "dev": true, 290 | "requires": { 291 | "duplexer": "~0.1.1", 292 | "from": "~0", 293 | "map-stream": "~0.1.0", 294 | "pause-stream": "0.0.11", 295 | "split": "0.3", 296 | "stream-combiner": "~0.0.4", 297 | "through": "~2.3.1" 298 | } 299 | }, 300 | "express": { 301 | "version": "4.17.1", 302 | "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", 303 | "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", 304 | "dev": true, 305 | "requires": { 306 | "accepts": "~1.3.7", 307 | "array-flatten": "1.1.1", 308 | "body-parser": "1.19.0", 309 | "content-disposition": "0.5.3", 310 | "content-type": "~1.0.4", 311 | "cookie": "0.4.0", 312 | "cookie-signature": "1.0.6", 313 | "debug": "2.6.9", 314 | "depd": "~1.1.2", 315 | "encodeurl": "~1.0.2", 316 | "escape-html": "~1.0.3", 317 | "etag": "~1.8.1", 318 | "finalhandler": "~1.1.2", 319 | "fresh": "0.5.2", 320 | "merge-descriptors": "1.0.1", 321 | "methods": "~1.1.2", 322 | "on-finished": "~2.3.0", 323 | "parseurl": "~1.3.3", 324 | "path-to-regexp": "0.1.7", 325 | "proxy-addr": "~2.0.5", 326 | "qs": "6.7.0", 327 | "range-parser": "~1.2.1", 328 | "safe-buffer": "5.1.2", 329 | "send": "0.17.1", 330 | "serve-static": "1.14.1", 331 | "setprototypeof": "1.1.1", 332 | "statuses": "~1.5.0", 333 | "type-is": "~1.6.18", 334 | "utils-merge": "1.0.1", 335 | "vary": "~1.1.2" 336 | } 337 | }, 338 | "finalhandler": { 339 | "version": "1.1.2", 340 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", 341 | "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", 342 | "dev": true, 343 | "requires": { 344 | "debug": "2.6.9", 345 | "encodeurl": "~1.0.2", 346 | "escape-html": "~1.0.3", 347 | "on-finished": "~2.3.0", 348 | "parseurl": "~1.3.3", 349 | "statuses": "~1.5.0", 350 | "unpipe": "~1.0.0" 351 | } 352 | }, 353 | "forwarded": { 354 | "version": "0.2.0", 355 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 356 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", 357 | "dev": true 358 | }, 359 | "fresh": { 360 | "version": "0.5.2", 361 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 362 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", 363 | "dev": true 364 | }, 365 | "from": { 366 | "version": "0.1.7", 367 | "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", 368 | "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=", 369 | "dev": true 370 | }, 371 | "fs-extra": { 372 | "version": "10.0.0", 373 | "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz", 374 | "integrity": "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==", 375 | "dev": true, 376 | "requires": { 377 | "graceful-fs": "^4.2.0", 378 | "jsonfile": "^6.0.1", 379 | "universalify": "^2.0.0" 380 | } 381 | }, 382 | "graceful-fs": { 383 | "version": "4.2.8", 384 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", 385 | "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", 386 | "dev": true 387 | }, 388 | "http-errors": { 389 | "version": "1.7.2", 390 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", 391 | "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", 392 | "dev": true, 393 | "requires": { 394 | "depd": "~1.1.2", 395 | "inherits": "2.0.3", 396 | "setprototypeof": "1.1.1", 397 | "statuses": ">= 1.5.0 < 2", 398 | "toidentifier": "1.0.0" 399 | } 400 | }, 401 | "iconv-lite": { 402 | "version": "0.4.24", 403 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 404 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 405 | "dev": true, 406 | "requires": { 407 | "safer-buffer": ">= 2.1.2 < 3" 408 | } 409 | }, 410 | "inherits": { 411 | "version": "2.0.3", 412 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 413 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", 414 | "dev": true 415 | }, 416 | "ipaddr.js": { 417 | "version": "1.9.1", 418 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 419 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", 420 | "dev": true 421 | }, 422 | "isexe": { 423 | "version": "2.0.0", 424 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 425 | "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", 426 | "dev": true 427 | }, 428 | "jsonfile": { 429 | "version": "6.1.0", 430 | "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", 431 | "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", 432 | "dev": true, 433 | "requires": { 434 | "graceful-fs": "^4.1.6", 435 | "universalify": "^2.0.0" 436 | } 437 | }, 438 | "map-stream": { 439 | "version": "0.1.0", 440 | "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", 441 | "integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=", 442 | "dev": true 443 | }, 444 | "media-typer": { 445 | "version": "0.3.0", 446 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 447 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", 448 | "dev": true 449 | }, 450 | "merge-descriptors": { 451 | "version": "1.0.1", 452 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 453 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", 454 | "dev": true 455 | }, 456 | "methods": { 457 | "version": "1.1.2", 458 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 459 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", 460 | "dev": true 461 | }, 462 | "mime": { 463 | "version": "1.6.0", 464 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 465 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", 466 | "dev": true 467 | }, 468 | "mime-db": { 469 | "version": "1.51.0", 470 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", 471 | "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==", 472 | "dev": true 473 | }, 474 | "mime-types": { 475 | "version": "2.1.34", 476 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", 477 | "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", 478 | "dev": true, 479 | "requires": { 480 | "mime-db": "1.51.0" 481 | } 482 | }, 483 | "ms": { 484 | "version": "2.0.0", 485 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 486 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", 487 | "dev": true 488 | }, 489 | "negotiator": { 490 | "version": "0.6.2", 491 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", 492 | "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", 493 | "dev": true 494 | }, 495 | "node-cleanup": { 496 | "version": "2.1.2", 497 | "resolved": "https://registry.npmjs.org/node-cleanup/-/node-cleanup-2.1.2.tgz", 498 | "integrity": "sha1-esGavSl+Caf3KnFUXZUbUX5N3iw=", 499 | "dev": true 500 | }, 501 | "object-assign": { 502 | "version": "4.1.1", 503 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 504 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", 505 | "dev": true 506 | }, 507 | "on-finished": { 508 | "version": "2.3.0", 509 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 510 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 511 | "dev": true, 512 | "requires": { 513 | "ee-first": "1.1.1" 514 | } 515 | }, 516 | "on-headers": { 517 | "version": "1.0.2", 518 | "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", 519 | "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", 520 | "dev": true 521 | }, 522 | "parseurl": { 523 | "version": "1.3.3", 524 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 525 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", 526 | "dev": true 527 | }, 528 | "path-key": { 529 | "version": "3.1.1", 530 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 531 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 532 | "dev": true 533 | }, 534 | "path-to-regexp": { 535 | "version": "0.1.7", 536 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 537 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", 538 | "dev": true 539 | }, 540 | "pause-stream": { 541 | "version": "0.0.11", 542 | "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", 543 | "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", 544 | "dev": true, 545 | "requires": { 546 | "through": "~2.3" 547 | } 548 | }, 549 | "proxy-addr": { 550 | "version": "2.0.7", 551 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 552 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 553 | "dev": true, 554 | "requires": { 555 | "forwarded": "0.2.0", 556 | "ipaddr.js": "1.9.1" 557 | } 558 | }, 559 | "ps-tree": { 560 | "version": "1.2.0", 561 | "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.2.0.tgz", 562 | "integrity": "sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==", 563 | "dev": true, 564 | "requires": { 565 | "event-stream": "=3.3.4" 566 | } 567 | }, 568 | "qs": { 569 | "version": "6.7.0", 570 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", 571 | "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", 572 | "dev": true 573 | }, 574 | "range-parser": { 575 | "version": "1.2.1", 576 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 577 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", 578 | "dev": true 579 | }, 580 | "raw-body": { 581 | "version": "2.4.0", 582 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", 583 | "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", 584 | "dev": true, 585 | "requires": { 586 | "bytes": "3.1.0", 587 | "http-errors": "1.7.2", 588 | "iconv-lite": "0.4.24", 589 | "unpipe": "1.0.0" 590 | }, 591 | "dependencies": { 592 | "bytes": { 593 | "version": "3.1.0", 594 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", 595 | "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", 596 | "dev": true 597 | } 598 | } 599 | }, 600 | "safe-buffer": { 601 | "version": "5.1.2", 602 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 603 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", 604 | "dev": true 605 | }, 606 | "safer-buffer": { 607 | "version": "2.1.2", 608 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 609 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 610 | "dev": true 611 | }, 612 | "send": { 613 | "version": "0.17.1", 614 | "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", 615 | "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", 616 | "dev": true, 617 | "requires": { 618 | "debug": "2.6.9", 619 | "depd": "~1.1.2", 620 | "destroy": "~1.0.4", 621 | "encodeurl": "~1.0.2", 622 | "escape-html": "~1.0.3", 623 | "etag": "~1.8.1", 624 | "fresh": "0.5.2", 625 | "http-errors": "~1.7.2", 626 | "mime": "1.6.0", 627 | "ms": "2.1.1", 628 | "on-finished": "~2.3.0", 629 | "range-parser": "~1.2.1", 630 | "statuses": "~1.5.0" 631 | }, 632 | "dependencies": { 633 | "ms": { 634 | "version": "2.1.1", 635 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 636 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", 637 | "dev": true 638 | } 639 | } 640 | }, 641 | "serve-static": { 642 | "version": "1.14.1", 643 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", 644 | "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", 645 | "dev": true, 646 | "requires": { 647 | "encodeurl": "~1.0.2", 648 | "escape-html": "~1.0.3", 649 | "parseurl": "~1.3.3", 650 | "send": "0.17.1" 651 | } 652 | }, 653 | "setprototypeof": { 654 | "version": "1.1.1", 655 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", 656 | "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", 657 | "dev": true 658 | }, 659 | "shebang-command": { 660 | "version": "2.0.0", 661 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 662 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 663 | "dev": true, 664 | "requires": { 665 | "shebang-regex": "^3.0.0" 666 | } 667 | }, 668 | "shebang-regex": { 669 | "version": "3.0.0", 670 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 671 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 672 | "dev": true 673 | }, 674 | "split": { 675 | "version": "0.3.3", 676 | "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", 677 | "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=", 678 | "dev": true, 679 | "requires": { 680 | "through": "2" 681 | } 682 | }, 683 | "statuses": { 684 | "version": "1.5.0", 685 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 686 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", 687 | "dev": true 688 | }, 689 | "stream-combiner": { 690 | "version": "0.0.4", 691 | "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", 692 | "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=", 693 | "dev": true, 694 | "requires": { 695 | "duplexer": "~0.1.1" 696 | } 697 | }, 698 | "string-argv": { 699 | "version": "0.1.2", 700 | "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.1.2.tgz", 701 | "integrity": "sha512-mBqPGEOMNJKXRo7z0keX0wlAhbBAjilUdPW13nN0PecVryZxdHIeM7TqbsSUA7VYuS00HGC6mojP7DlQzfa9ZA==", 702 | "dev": true 703 | }, 704 | "strip-ansi": { 705 | "version": "6.0.1", 706 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 707 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 708 | "dev": true, 709 | "requires": { 710 | "ansi-regex": "^5.0.1" 711 | } 712 | }, 713 | "through": { 714 | "version": "2.3.8", 715 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", 716 | "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", 717 | "dev": true 718 | }, 719 | "toidentifier": { 720 | "version": "1.0.0", 721 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", 722 | "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", 723 | "dev": true 724 | }, 725 | "tsc-watch": { 726 | "version": "4.5.0", 727 | "resolved": "https://registry.npmjs.org/tsc-watch/-/tsc-watch-4.5.0.tgz", 728 | "integrity": "sha512-aXhN4jY+1YEcn/NwCQ/+fHqU43EqOpW+pS+933EPsVEsrKhvyrodPDIjQsk1a1niFrabAK3RIBrRbAslVefEbQ==", 729 | "dev": true, 730 | "requires": { 731 | "cross-spawn": "^7.0.3", 732 | "node-cleanup": "^2.1.2", 733 | "ps-tree": "^1.2.0", 734 | "string-argv": "^0.1.1", 735 | "strip-ansi": "^6.0.0" 736 | } 737 | }, 738 | "type-is": { 739 | "version": "1.6.18", 740 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 741 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 742 | "dev": true, 743 | "requires": { 744 | "media-typer": "0.3.0", 745 | "mime-types": "~2.1.24" 746 | } 747 | }, 748 | "typescript": { 749 | "version": "4.5.2", 750 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.2.tgz", 751 | "integrity": "sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw==", 752 | "dev": true 753 | }, 754 | "universalify": { 755 | "version": "2.0.0", 756 | "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", 757 | "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", 758 | "dev": true 759 | }, 760 | "unpipe": { 761 | "version": "1.0.0", 762 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 763 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", 764 | "dev": true 765 | }, 766 | "utils-merge": { 767 | "version": "1.0.1", 768 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 769 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", 770 | "dev": true 771 | }, 772 | "uuid": { 773 | "version": "8.3.2", 774 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", 775 | "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", 776 | "dev": true 777 | }, 778 | "vary": { 779 | "version": "1.1.2", 780 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 781 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", 782 | "dev": true 783 | }, 784 | "which": { 785 | "version": "2.0.2", 786 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 787 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 788 | "dev": true, 789 | "requires": { 790 | "isexe": "^2.0.0" 791 | } 792 | } 793 | } 794 | } 795 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "tsc-watch -p ./ --outDir ./dist --onSuccess \"node ./dist/server.js\" --onFailure \"echo Beep! Compilation Failed\" --compiler typescript/bin/tsc" 9 | }, 10 | "dependencies": { 11 | "@types/compression": "^1.7.2", 12 | "@types/cors": "^2.8.12", 13 | "@types/express": "^4.17.13" 14 | }, 15 | "devDependencies": { 16 | "@types/body-parser": "^1.19.2", 17 | "@types/fs-extra": "^9.0.13", 18 | "@types/uuid": "^8.3.3", 19 | "compression": "^1.7.4", 20 | "cors": "^2.8.5", 21 | "dotenv": "^10.0.0", 22 | "express": "^4.17.1", 23 | "fs-extra": "^10.0.0", 24 | "tsc-watch": "^4.5.0", 25 | "typescript": "^4.5.2", 26 | "uuid": "^8.3.2" 27 | }, 28 | "author": "Max Maximov", 29 | "license": "ISC" 30 | } 31 | -------------------------------------------------------------------------------- /server/src/helpers/server.helper.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import * as compression from 'compression'; 3 | 4 | export default function shouldCompress(req: Request, res: Response) { 5 | if (req.headers['x-no-compression']) return false; 6 | 7 | return compression.filter(req, res); 8 | } 9 | -------------------------------------------------------------------------------- /server/src/models/example.model.ts: -------------------------------------------------------------------------------- 1 | export interface ExampleModel { 2 | id: string; 3 | name: string; 4 | email?: string; 5 | } 6 | -------------------------------------------------------------------------------- /server/src/routers/example.router.ts: -------------------------------------------------------------------------------- 1 | import express, { Request, Response } from 'express'; 2 | import fsExtra from 'fs-extra'; 3 | import path from 'path'; 4 | import { v4 as uuidv4 } from 'uuid'; 5 | import { ExampleModel } from '../models/example.model'; 6 | 7 | const exampleRouter = express.Router(); 8 | const examplePath = path.join(__dirname, '../../data', 'example.json'); 9 | 10 | exampleRouter.get('/', async (req: Request, res: Response) => { 11 | const { limit } = req.query; 12 | 13 | try { 14 | let examples = await fsExtra.readJson(examplePath); 15 | 16 | res.status(200).json(examples.slice(0, Number.isInteger(limit) ? limit : examples.length)); 17 | } catch (e) { 18 | res.status(500).json({}); 19 | } 20 | }); 21 | 22 | exampleRouter.get('/:id', async (req: Request, res: Response) => { 23 | const { id } = req.params; 24 | 25 | try { 26 | const examples = await fsExtra.readJson(examplePath); 27 | const example = examples.find((example: ExampleModel) => example.id === id); 28 | 29 | res.status(200).json(example); 30 | } catch (e) { 31 | res.status(500).json({}); 32 | } 33 | }); 34 | 35 | exampleRouter.post('/', async (req: Request, res: Response) => { 36 | const { body } = req; 37 | const uniqueUserId = uuidv4(); 38 | const exampleValue = { 39 | id: uniqueUserId, 40 | ...body, 41 | }; 42 | 43 | try { 44 | let examples = await fsExtra.readJson(examplePath); 45 | examples.push(exampleValue); 46 | 47 | await fsExtra.writeJson(examplePath, examples); 48 | 49 | res.status(201).json(exampleValue); 50 | } catch (e) { 51 | res.status(500).json({}); 52 | } 53 | }); 54 | 55 | exampleRouter.delete('/:id', async (req: Request, res: Response) => { 56 | const { id } = req.params; 57 | 58 | try { 59 | let examples = await fsExtra.readJson(examplePath); 60 | const deletedUser = examples.find((example: ExampleModel) => example.id === id); 61 | 62 | await fsExtra.writeJson( 63 | examplePath, 64 | examples.filter((i: ExampleModel) => i.id !== id) 65 | ); 66 | 67 | res.status(200).json(deletedUser); 68 | } catch (e) { 69 | res.status(500).json({}); 70 | } 71 | }); 72 | 73 | exampleRouter.put('/', async (req: Request, res: Response) => { 74 | const { body } = req; 75 | const id = body.id; 76 | 77 | try { 78 | const examples = await fsExtra.readJson(examplePath); 79 | const example = examples.find((example: ExampleModel) => example.id === id); 80 | const updatedUsers = examples.map((example: ExampleModel) => { 81 | if (example.id === id) return { ...example, ...body }; 82 | 83 | return example; 84 | }); 85 | 86 | await fsExtra.writeJson(examplePath, updatedUsers); 87 | 88 | res.status(200).json(example); 89 | } catch (e) { 90 | res.status(500).json({}); 91 | } 92 | }); 93 | 94 | export { exampleRouter }; 95 | -------------------------------------------------------------------------------- /server/src/routers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './example.router'; 2 | -------------------------------------------------------------------------------- /server/src/server.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import * as bodyParser from 'body-parser'; 3 | import * as routers from './routers'; 4 | import compression from 'compression'; 5 | import shouldCompress from './helpers/server.helper'; 6 | import cors from 'cors'; 7 | 8 | const app = express(), 9 | port = process.env.NODEJS_PORT || 3003, 10 | root = '/api/v1/', 11 | isProduction = process.env.NODE_ENV === 'production'; 12 | 13 | const appRouters = [ 14 | { 15 | url: 'example', 16 | middleware: routers.exampleRouter, 17 | }, 18 | ]; 19 | 20 | app.use( 21 | cors({ 22 | credentials: !isProduction, 23 | }) 24 | ); 25 | app.use(bodyParser.json()); 26 | 27 | app.use(compression({ filter: shouldCompress })); 28 | 29 | appRouters.forEach(router => app.use(root + router.url, router.middleware)); 30 | 31 | app.listen(port, () => { 32 | console.log(`Mock server is listening on port ${port}`); 33 | }); 34 | -------------------------------------------------------------------------------- /server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "lib": ["dom", "es6", "es2017", "esnext.asynciterable"], 6 | "sourceMap": true, 7 | "outDir": "./dist", 8 | "moduleResolution": "node", 9 | "removeComments": true, 10 | "skipLibCheck": true, 11 | "noImplicitAny": true, 12 | "strictNullChecks": true, 13 | "strictFunctionTypes": true, 14 | "noImplicitThis": true, 15 | "noUnusedLocals": true, 16 | "noUnusedParameters": false, 17 | "noImplicitReturns": true, 18 | "noFallthroughCasesInSwitch": true, 19 | "allowSyntheticDefaultImports": true, 20 | "esModuleInterop": true, 21 | "emitDecoratorMetadata": true, 22 | "experimentalDecorators": true, 23 | "resolveJsonModule": true, 24 | "baseUrl": "." 25 | }, 26 | "exclude": ["node_modules"], 27 | "include": ["./src/server.ts"] 28 | } 29 | -------------------------------------------------------------------------------- /src/components/app/app.container.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react'; 2 | import { Provider } from 'react-redux'; 3 | import { store } from '../../store/store'; 4 | import ExampleListContainer from '../example/list/example-list.container'; 5 | 6 | type AppContainerProps = {}; 7 | 8 | const AppContainer = memo(function AppContainer() { 9 | return ( 10 | 11 | 12 | 13 | ); 14 | }); 15 | 16 | export default AppContainer; 17 | -------------------------------------------------------------------------------- /src/components/example/example.component.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo, useState } from 'react'; 2 | import { ExampleModel } from '../../models/example.model'; 3 | import styles from './example.module.css'; 4 | 5 | type NcExampleProps = { 6 | model: ExampleModel; 7 | deleteExample?: (example: ExampleModel) => void; 8 | updateExample?: (example: ExampleModel) => void; 9 | }; 10 | 11 | const NcExample = memo(function NcExample({ model, deleteExample, updateExample }) { 12 | const { name } = model; 13 | const [edit, setEdit] = useState(); 14 | const [value, setValue] = useState(name); 15 | 16 | return edit ? ( 17 | <> 18 | { 22 | const value = e.target.value.trim(); 23 | 24 | if (value) setValue(value); 25 | }} 26 | /> 27 | 37 | 45 | 46 | ) : ( 47 | <> 48 | {name} 49 | 58 | 61 | 62 | ); 63 | }); 64 | 65 | export default NcExample; 66 | -------------------------------------------------------------------------------- /src/components/example/example.module.css: -------------------------------------------------------------------------------- 1 | .text { 2 | margin-right: 1rem; 3 | } 4 | -------------------------------------------------------------------------------- /src/components/example/list/example-list.container.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react'; 2 | import styles from './example-list.module.css'; 3 | import { useExampleList } from './example-list.hook'; 4 | import NcExample from '../example.component'; 5 | 6 | type ExamplesContainerProps = {}; 7 | 8 | const ExampleListContainer = memo(function ExamplesContainer() { 9 | const { ref, examples, loading, createExample, deleteExample, updateExample } = useExampleList(); 10 | 11 | return ( 12 |
13 |

Example list

14 |
15 | 16 | 19 |
20 |
21 | {loading ? ( 22 | <>Loading... 23 | ) : ( 24 |
    25 | {examples.map(example => ( 26 |
  • 27 | 32 |
  • 33 | ))} 34 |
35 | )} 36 |
37 |
38 | ); 39 | }); 40 | 41 | export default ExampleListContainer; 42 | -------------------------------------------------------------------------------- /src/components/example/list/example-list.hook.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useRef } from 'react'; 2 | import { ExampleModel } from '../../../models/example.model'; 3 | import { exampleApi } from '../../../store/example/example.api'; 4 | 5 | export function useExampleList() { 6 | const { data: examples = [], isLoading: examplesLoading } = exampleApi.useFetchExampleListQuery(); 7 | const [createExampleMutation, { isLoading: createExampleLoading }] = exampleApi.useCreateExampleMutation(); 8 | const [deleteExampleMutation, { isLoading: deleteExampleLoading }] = exampleApi.useDeleteExampleMutation(); 9 | const [updateExampleMutation, { isLoading: updateExampleLoading }] = exampleApi.useUpdateExampleMutation(); 10 | 11 | const ref = useRef(null); 12 | 13 | const loading = examplesLoading || createExampleLoading || deleteExampleLoading || updateExampleLoading; 14 | 15 | const createExample = useCallback(() => { 16 | const value = ref.current?.value.trim(); 17 | 18 | if (value) 19 | createExampleMutation({ 20 | example: { name: value }, 21 | }); 22 | }, [createExampleMutation]); 23 | 24 | const updateExample = useCallback( 25 | (example: ExampleModel) => updateExampleMutation({ example }), 26 | [updateExampleMutation] 27 | ); 28 | 29 | const deleteExample = useCallback( 30 | (example: ExampleModel) => deleteExampleMutation({ example }), 31 | [deleteExampleMutation] 32 | ); 33 | 34 | return { 35 | ref, 36 | examples, 37 | loading, 38 | createExample, 39 | updateExample, 40 | deleteExample, 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /src/components/example/list/example-list.module.css: -------------------------------------------------------------------------------- 1 | .host { 2 | margin: 2rem; 3 | } 4 | 5 | .block { 6 | width: 30vw; 7 | margin: 2rem; 8 | } 9 | 10 | .list { 11 | list-style-type: none; 12 | } 13 | 14 | .listItem { 15 | margin-bottom: 2rem; 16 | } 17 | -------------------------------------------------------------------------------- /src/constants/api.constants.ts: -------------------------------------------------------------------------------- 1 | export const BASE_URL = 'http://localhost:3003/api/v1'; 2 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 4 | 'Droid Sans', 'Helvetica Neue', sans-serif; 5 | -webkit-font-smoothing: antialiased; 6 | -moz-osx-font-smoothing: grayscale; 7 | } 8 | 9 | code { 10 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; 11 | } 12 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import AppContainer from './components/app/app.container'; 5 | 6 | ReactDOM.render( 7 | 8 | 9 | , 10 | document.getElementById('root') 11 | ); 12 | 13 | // If you want to start measuring performance in your app, pass a function 14 | // to log results (for example: reportWebVitals(console.log)) 15 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 16 | // reportWebVitals(); 17 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/models/example.model.ts: -------------------------------------------------------------------------------- 1 | export interface ExampleModel { 2 | id: string; 3 | name: string; 4 | email?: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/reportWebVitals.ts: -------------------------------------------------------------------------------- 1 | import { ReportHandler } from 'web-vitals'; 2 | 3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => { 4 | if (onPerfEntry && onPerfEntry instanceof Function) { 5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 6 | getCLS(onPerfEntry); 7 | getFID(onPerfEntry); 8 | getFCP(onPerfEntry); 9 | getLCP(onPerfEntry); 10 | getTTFB(onPerfEntry); 11 | }); 12 | } 13 | }; 14 | 15 | export default reportWebVitals; 16 | -------------------------------------------------------------------------------- /src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /src/store/common.api.ts: -------------------------------------------------------------------------------- 1 | import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/dist/query/react'; 2 | import { BASE_URL } from '../constants/api.constants'; 3 | 4 | export const commonApi = createApi({ 5 | reducerPath: 'api', 6 | baseQuery: fetchBaseQuery({ 7 | baseUrl: BASE_URL, 8 | prepareHeaders: headers => { 9 | headers.set('Content-Type', 'application/json;charset=UTF-8'); 10 | headers.set('Authorization', 'anonymous'); 11 | 12 | return headers; 13 | }, 14 | }), 15 | tagTypes: ['Example'], 16 | endpoints: _ => ({}), 17 | }); 18 | -------------------------------------------------------------------------------- /src/store/example/example.api.ts: -------------------------------------------------------------------------------- 1 | import { ExampleModel } from '../../models/example.model'; 2 | import { commonApi } from '../common.api'; 3 | 4 | export const exampleApi = commonApi.injectEndpoints({ 5 | endpoints: build => ({ 6 | fetchExampleList: build.query({ 7 | query: (limit: number = 5) => ({ 8 | url: '/example', 9 | params: { 10 | limit, 11 | }, 12 | }), 13 | providesTags: result => [{ type: 'Example', id: 'List' }], 14 | }), 15 | createExample: build.mutation & { limit?: number } }>({ 16 | query: ({ example }) => ({ 17 | url: '/example', 18 | method: 'POST', 19 | body: example, 20 | }), 21 | async onQueryStarted({ example }, { dispatch, queryFulfilled }) { 22 | try { 23 | const { data } = await queryFulfilled; 24 | 25 | dispatch( 26 | exampleApi.util.updateQueryData('fetchExampleList', example.limit, draft => { 27 | draft.unshift(data); 28 | }) 29 | ); 30 | } catch (e) { 31 | console.error('userApi createUser error', e); 32 | } 33 | }, 34 | }), 35 | updateExample: build.mutation({ 36 | query: ({ example }) => ({ 37 | url: `/example`, 38 | method: 'PUT', 39 | body: example, 40 | }), 41 | invalidatesTags: ['Example'], 42 | }), 43 | deleteExample: build.mutation({ 44 | query: ({ example }) => ({ 45 | url: `/example/${example.id}`, 46 | method: 'DELETE', 47 | }), 48 | invalidatesTags: ['Example'], 49 | }), 50 | }), 51 | }); 52 | -------------------------------------------------------------------------------- /src/store/store.ts: -------------------------------------------------------------------------------- 1 | import { configureStore } from '@reduxjs/toolkit'; 2 | import { combineReducers } from 'redux'; 3 | import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'; 4 | import { commonApi } from './common.api'; 5 | 6 | const preloadedState = {}; 7 | 8 | const rootReducer = combineReducers({ 9 | [commonApi.reducerPath]: commonApi.reducer, 10 | }); 11 | 12 | export const store = configureStore({ 13 | reducer: rootReducer, 14 | middleware: getDefaultMiddleware => getDefaultMiddleware().concat(commonApi.middleware), 15 | preloadedState, 16 | devTools: process.env.NODE_ENV !== 'production', 17 | }); 18 | 19 | export type RootState = ReturnType; 20 | export type AppDispatch = typeof store.dispatch; 21 | 22 | export const useAppDispatch = () => useDispatch(); 23 | export const useAppSelector: TypedUseSelectorHook = useSelector; 24 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | --------------------------------------------------------------------------------