├── .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 |
--------------------------------------------------------------------------------