├── .gitignore ├── README.md ├── database.yml ├── package-lock.json ├── package.json ├── public └── index.html └── server.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | .idea -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Redux Saga Shopping Cart Back-end Server 2 | 3 | ## About 4 | This server is meant to mock the functionality of a real shopping application's back-end services. It comes ready-made and complete, but with no front-end application to consume its APIs. That task is left to you. 5 | 6 | ### Demographics 7 | This application is meant to be used by, but not limited to, students completing Daniel Stern's Redux Saga course on Pluralsight. 8 | 9 | ### Usage 10 | 1. Install dependencies: `npm install` 11 | 2. Run server. `npm start` 12 | 3. Navigate to the instructions page: `http://localhost:8081` (You must not already have a different application running on this port) -------------------------------------------------------------------------------- /database.yml: -------------------------------------------------------------------------------- 1 | users: 2 | - id: U10000 3 | name: J.R.R Hemingway 4 | country: CAD 5 | address1: 555 La Floridita Way 6 | phone: 15551234567 7 | 8 | - id: U20000 9 | name: George R. R. Rowling 10 | country: USD 11 | address1: 14 Diagon Alley 12 | 13 | cards: 14 | - id: C10000 15 | owner: U10000 16 | number: 4500123456787777 17 | availableFunds: 500 18 | 19 | carts: 20 | - owner: U10000 21 | items: 22 | - id: I10000 23 | quantity: 2 24 | - id: I20000 25 | quantity: 1 26 | - id: I40000 27 | quantity: 3 28 | 29 | - owner: U20000 30 | items: 31 | - id: I50000 32 | quantity: 3 33 | 34 | items: 35 | - id: I10000 36 | name: Velvet Mousepad 37 | description: Your mouse never had it so good. 38 | usd: 129.95 39 | cad: 175.85 40 | img: velvet-mousepad.png 41 | quantityAvailable: 100000 42 | weight: 0.8 43 | 44 | - id: I20000 45 | name: Wood-Grain USB Cable 46 | description: This cable matches any mahogany or oak-based computer chassis. 47 | usd: 35.50 48 | cad: 55.60 49 | img: usb-cable.png 50 | quantityAvailable: 3 51 | weight: 0.2 52 | 53 | - id: I30000 54 | name: 1 Year Subscription - JavaScript Enthusiast Magazine 55 | description: Finally, JavaScript news, delivered right to your front door! 56 | usd: 119.15 57 | cad: 119.15 58 | quantityAvailable: 99999 59 | weight: 0 60 | 61 | - id: I40000 62 | name: Teaching Computers to Golf, 1st Edition 63 | description: Don't try to teach your Mac or PC how to play golf without it. 64 | usd: 15.95 65 | cad: 22.95 66 | quantityAvailable: 5 67 | weight: 0.5 68 | 69 | - id: I50000 70 | name: I <3 Yield Pin 71 | description: Show your friends how much you love JavaScript's newest keyword! 72 | usd: 1.95 73 | cad: 2.95 74 | quantityAvailable: 0 75 | weight: 0.1 76 | 77 | taxRates: 78 | - symbol: USD 79 | rate: 0.15 80 | - symbol: CAD 81 | rate: 0.19 82 | 83 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redux-saga-cart-server", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "dependencies": { 6 | "accepts": { 7 | "version": "1.3.3", 8 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.3.tgz", 9 | "integrity": "sha1-w8p0NJOGSMPg2cHjKN1otiLChMo=" 10 | }, 11 | "argparse": { 12 | "version": "1.0.9", 13 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz", 14 | "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=" 15 | }, 16 | "array-flatten": { 17 | "version": "1.1.1", 18 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 19 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 20 | }, 21 | "balanced-match": { 22 | "version": "1.0.0", 23 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 24 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" 25 | }, 26 | "brace-expansion": { 27 | "version": "1.1.8", 28 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", 29 | "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=" 30 | }, 31 | "chance": { 32 | "version": "1.0.10", 33 | "resolved": "https://registry.npmjs.org/chance/-/chance-1.0.10.tgz", 34 | "integrity": "sha1-A1ALBK2U53jdKJGwnsc6ath7GZY=" 35 | }, 36 | "concat-map": { 37 | "version": "0.0.1", 38 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 39 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" 40 | }, 41 | "content-disposition": { 42 | "version": "0.5.2", 43 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", 44 | "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" 45 | }, 46 | "content-type": { 47 | "version": "1.0.2", 48 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.2.tgz", 49 | "integrity": "sha1-t9ETrueo3Se9IRM8TcJSnfFyHu0=" 50 | }, 51 | "cookie": { 52 | "version": "0.3.1", 53 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", 54 | "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" 55 | }, 56 | "cookie-signature": { 57 | "version": "1.0.6", 58 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 59 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 60 | }, 61 | "cors": { 62 | "version": "2.8.3", 63 | "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.3.tgz", 64 | "integrity": "sha1-TPeOHSMymnSWsvwiJbd8pbteuAI=" 65 | }, 66 | "debug": { 67 | "version": "2.6.7", 68 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.7.tgz", 69 | "integrity": "sha1-krrR9tBbu2u6Isyoi80OyJTChh4=" 70 | }, 71 | "depd": { 72 | "version": "1.1.0", 73 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz", 74 | "integrity": "sha1-4b2Cxqq2ztlluXuIsX7T5SjKGMM=" 75 | }, 76 | "destroy": { 77 | "version": "1.0.4", 78 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 79 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 80 | }, 81 | "ee-first": { 82 | "version": "1.1.1", 83 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 84 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 85 | }, 86 | "encodeurl": { 87 | "version": "1.0.1", 88 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", 89 | "integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA=" 90 | }, 91 | "escape-html": { 92 | "version": "1.0.3", 93 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 94 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 95 | }, 96 | "etag": { 97 | "version": "1.8.0", 98 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.0.tgz", 99 | "integrity": "sha1-b2Ma7zNtbEY2K1F2QETOIWvjwFE=" 100 | }, 101 | "express": { 102 | "version": "4.15.3", 103 | "resolved": "https://registry.npmjs.org/express/-/express-4.15.3.tgz", 104 | "integrity": "sha1-urZdDwOqgMNYQIly/HAPkWlEtmI=" 105 | }, 106 | "finalhandler": { 107 | "version": "1.0.3", 108 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.0.3.tgz", 109 | "integrity": "sha1-70fneVDpmXgOhgIqVg4yF+DQzIk=" 110 | }, 111 | "forwarded": { 112 | "version": "0.1.0", 113 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.0.tgz", 114 | "integrity": "sha1-Ge+YdMSuHCl7zweP3mOgm2aoQ2M=" 115 | }, 116 | "fresh": { 117 | "version": "0.5.0", 118 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.0.tgz", 119 | "integrity": "sha1-9HTKXmqSRtb9jglTz6m5yAWvp44=" 120 | }, 121 | "fs.realpath": { 122 | "version": "1.0.0", 123 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 124 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" 125 | }, 126 | "glob": { 127 | "version": "7.1.2", 128 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", 129 | "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==" 130 | }, 131 | "http-errors": { 132 | "version": "1.6.1", 133 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.1.tgz", 134 | "integrity": "sha1-X4uO2YrKVFZWv1cplzh/kEpyIlc=" 135 | }, 136 | "inflight": { 137 | "version": "1.0.6", 138 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 139 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=" 140 | }, 141 | "inherits": { 142 | "version": "2.0.3", 143 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 144 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 145 | }, 146 | "ipaddr.js": { 147 | "version": "1.3.0", 148 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.3.0.tgz", 149 | "integrity": "sha1-HgOlL9rYOou7KyXL9JmLTP/NPew=" 150 | }, 151 | "media-typer": { 152 | "version": "0.3.0", 153 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 154 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 155 | }, 156 | "merge-descriptors": { 157 | "version": "1.0.1", 158 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 159 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 160 | }, 161 | "methods": { 162 | "version": "1.1.2", 163 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 164 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 165 | }, 166 | "mime": { 167 | "version": "1.3.4", 168 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz", 169 | "integrity": "sha1-EV+eO2s9rylZmDyzjxSaLUDrXVM=" 170 | }, 171 | "mime-db": { 172 | "version": "1.27.0", 173 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz", 174 | "integrity": "sha1-gg9XIpa70g7CXtVeW13oaeVDbrE=" 175 | }, 176 | "mime-types": { 177 | "version": "2.1.15", 178 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz", 179 | "integrity": "sha1-pOv1BkCUVpI3uM9wBGd20J/JKu0=" 180 | }, 181 | "minimatch": { 182 | "version": "3.0.4", 183 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 184 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==" 185 | }, 186 | "ms": { 187 | "version": "2.0.0", 188 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 189 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 190 | }, 191 | "negotiator": { 192 | "version": "0.6.1", 193 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", 194 | "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" 195 | }, 196 | "object-assign": { 197 | "version": "4.1.1", 198 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 199 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" 200 | }, 201 | "on-finished": { 202 | "version": "2.3.0", 203 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 204 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=" 205 | }, 206 | "once": { 207 | "version": "1.4.0", 208 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 209 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=" 210 | }, 211 | "parseurl": { 212 | "version": "1.3.1", 213 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.1.tgz", 214 | "integrity": "sha1-yKuMkiO6NIiKpkopeyiFO+wY2lY=" 215 | }, 216 | "path-is-absolute": { 217 | "version": "1.0.1", 218 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 219 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" 220 | }, 221 | "path-to-regexp": { 222 | "version": "0.1.7", 223 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 224 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 225 | }, 226 | "proxy-addr": { 227 | "version": "1.1.4", 228 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.1.4.tgz", 229 | "integrity": "sha1-J+VF9pYKRKYn2bREZ+NcG2tM4vM=" 230 | }, 231 | "qs": { 232 | "version": "6.4.0", 233 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", 234 | "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=" 235 | }, 236 | "range-parser": { 237 | "version": "1.2.0", 238 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", 239 | "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" 240 | }, 241 | "send": { 242 | "version": "0.15.3", 243 | "resolved": "https://registry.npmjs.org/send/-/send-0.15.3.tgz", 244 | "integrity": "sha1-UBP5+ZAj31DRvZiSwZ4979HVMwk=" 245 | }, 246 | "serve-static": { 247 | "version": "1.12.3", 248 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.12.3.tgz", 249 | "integrity": "sha1-n0uhni8wMMVH+K+ZEHg47DjVseI=" 250 | }, 251 | "setprototypeof": { 252 | "version": "1.0.3", 253 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", 254 | "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" 255 | }, 256 | "sprintf-js": { 257 | "version": "1.0.3", 258 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", 259 | "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" 260 | }, 261 | "statuses": { 262 | "version": "1.3.1", 263 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", 264 | "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" 265 | }, 266 | "type-is": { 267 | "version": "1.6.15", 268 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", 269 | "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=" 270 | }, 271 | "unpipe": { 272 | "version": "1.0.0", 273 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 274 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 275 | }, 276 | "utils-merge": { 277 | "version": "1.0.0", 278 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz", 279 | "integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg=" 280 | }, 281 | "vary": { 282 | "version": "1.1.1", 283 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.1.tgz", 284 | "integrity": "sha1-Z1Neu2lMHVIldFeYRmUyP1h+jTc=" 285 | }, 286 | "wrappy": { 287 | "version": "1.0.2", 288 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 289 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 290 | }, 291 | "yamljs": { 292 | "version": "0.2.10", 293 | "resolved": "https://registry.npmjs.org/yamljs/-/yamljs-0.2.10.tgz", 294 | "integrity": "sha1-SBzHwlynOvWfWR8MluPOVsdXpA8=" 295 | } 296 | } 297 | } 298 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redux-saga-cart-server", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "node server.js" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "chance": "^1.0.10", 15 | "cors": "^2.8.3", 16 | "express": "^4.15.3", 17 | "yamljs": "^0.2.10" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Redux Cart Server 4 | 5 | 6 | 7 |

About

8 |

9 | A demo REST API to integrate with a Redux Saga shopping cart app. 10 |

11 | 12 |

13 | Routes 14 |

15 | 16 | 17 | 20 | 23 | 26 | 27 | 28 | 31 | 36 | 39 | 40 | 41 | 44 | 49 | 52 | 53 | 54 | 57 | 62 | 65 | 66 | 67 | 70 | 75 | 78 | 79 | 80 | 83 | 88 | 91 | 92 | 93 | 96 | 101 | 104 | 105 | 106 | 109 | 114 | 117 | 118 | 119 | 122 | 127 | 130 | 131 | 132 | 135 | 138 | 141 | 142 | 143 | 146 | 151 | 154 | 155 | 156 | 159 | 164 | 167 | 168 | 169 | 172 | 177 | 180 | 181 | 182 | 183 | 184 |
18 | route 19 | 21 | url 22 | 24 | about 25 |
29 | User 30 | 32 | 33 | /user/:id 34 | 35 | 37 | Returns data on the given user ID 38 |
42 | Cart 43 | 45 | 46 | /user/:owner 47 | 48 | 50 | Returns all items in user's cart, or an error if no cart exists. 51 |
55 | Cart Add Item 56 | 58 | 59 | /cart/add/:owner/:itemID 60 | 61 | 63 | Adds an item to the specified owner's cart with the specified ID. 64 |
68 | Cart Remove Item 69 | 71 | 72 | /cart/remove/:owner/:itemID 73 | 74 | 76 | Removes one unit of the specified item from the specified owner's cart. 77 |
81 | Items 82 | 84 | 85 | /items/:ids 86 | 87 | 89 | Returns metadata for a collection of comma-separated item IDS. 90 |
94 | Prices 95 | 97 | 98 | /prices/:symbol/:ids 99 | 100 | 102 | Returns the prices of the specified items in the specified symbol 103 |
107 | Shipping 108 | 110 | 111 | /shipping/:ids 112 | 113 | 115 | Calculates the shipping costs for a block of item IDS. 116 |
120 | Tax 121 | 123 | 124 | /tax/:symbol 125 | 126 | 128 | Returns the tax rate for the given currency or an error if one can't be found. 129 |
133 | Checkout (TBC) 134 | 136 | /checkout/:owner 137 | 139 | Purchases all the items in the cart, or purchases none of the items in the cart if any errors occur. 140 |
144 | Card (Validate) 145 | 147 | 148 | /card/validate/:owner 149 | 150 | 152 | Validates the owner's credit card - always returns {validated:true} 153 |
157 | Cart (Validate) 158 | 160 | 161 | /cart/validate/:owner 162 | 163 | 165 | Validates the cart - returns {validated:true} if all cart items are in stock 166 |
170 | Card (charge) 171 | 173 | 174 | /cart/charge/:owner 175 | 176 | 178 | Charges the user and purchases all items. Returns { success: true } if sufficient funds were available. 179 |
185 | 186 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const cors = require('cors'); 3 | const port = 8081; 4 | const app = new express(); 5 | YAML = require('yamljs'); 6 | 7 | const serverDelayConstant = 100; 8 | // Simulate a small amount of delay to demonstrate app's async features 9 | app.use((req,res,next)=>{ 10 | const delay = (Math.random() * 15 + 5) * serverDelayConstant; 11 | setTimeout(next,delay); 12 | }); 13 | 14 | app.use(express.static('public')); 15 | 16 | nativeObject = YAML.load('database.yml',(database)=>{ 17 | 18 | const makeCartAdjustmentRoute = (shouldAdd = true) => (req,res)=>{ 19 | const { owner, itemID } = req.params; 20 | const cart = database.carts.find(cart=>cart.owner === owner); 21 | if (!cart) { 22 | return res 23 | .status(500) 24 | .json({ 25 | error:"No cart found with the specified ID", 26 | owner 27 | }) 28 | 29 | } 30 | 31 | const item = database.items.find(item => item.id === itemID); 32 | if (!item) { 33 | return res 34 | .status(500) 35 | .json({ 36 | error:"No item found with the specified ID", 37 | itemID 38 | }) 39 | } 40 | 41 | 42 | const existingItem = cart.items.find(cartItem=>cartItem.id === itemID); 43 | if (existingItem) { 44 | if (shouldAdd && parseInt(existingItem.quantity) >= parseInt(item.quantityAvailable)) { 45 | return res.status(503) 46 | .json({ 47 | error:"An insufficient quantity of items remains.", 48 | itemID, 49 | quantityAvailable:item.quantityAvailable 50 | }); 51 | } 52 | existingItem.quantity += (shouldAdd ? 1 : -1); 53 | if (existingItem.quantity === 0) { 54 | cart.items = cart.items.filter(item=>item.id !== itemID); 55 | } 56 | } else { 57 | if (shouldAdd) { 58 | cart.items.push({ 59 | quantity:1, 60 | id:itemID 61 | }) 62 | } else { 63 | return res.status(500) 64 | .json({ 65 | error:"No item with the specified ID exists in the cart to be removed", 66 | owner, 67 | itemID 68 | }) 69 | } 70 | 71 | } 72 | res 73 | .status(200) 74 | .send(cart); 75 | } 76 | 77 | app.get("/cart/add/:owner/:itemID",makeCartAdjustmentRoute(true)); 78 | app.get("/cart/remove/:owner/:itemID",makeCartAdjustmentRoute(false)); 79 | 80 | app.get("/user/:id",(req,res)=>{ 81 | const id = req.params.id; 82 | const user = database.users.find(user=>user.id === id); 83 | if (!user) { 84 | return res 85 | .status(500) 86 | .json({ 87 | error:"No user with the specified ID", 88 | id 89 | }) 90 | } else { 91 | res 92 | .status(200) 93 | .json(user) 94 | } 95 | }); 96 | 97 | app.use(["/cart/validate/:owner","/cart/:owner","/card/charge/:owner"],(req,res,next)=>{ 98 | const { owner } = req.params; 99 | const cart = database.carts.find(cart=>cart.owner === owner); 100 | if (!cart){ 101 | return res 102 | .status(404) 103 | .json({error:"No cart with the specified owner",owner}); 104 | } 105 | else { 106 | req.cart = cart; 107 | next(); 108 | } 109 | }); 110 | 111 | app.get("/cart/validate/:owner",(req,res)=>{ 112 | const { items } = req.cart; 113 | let validated = true; 114 | let error = null; 115 | items.forEach(({id,quantity})=>{ 116 | const item = database.items.find(item => item.id === id); 117 | if (item.quantityAvailable < quantity) { 118 | validated = false; 119 | error = "There is an insufficient quantity of " + id; 120 | } 121 | });; 122 | res 123 | .status(200) 124 | .json({validated,error}); 125 | 126 | }); 127 | 128 | 129 | app.get("/cart/:owner",(req,res)=>{ 130 | const cart = req.cart; 131 | res 132 | .status(200) 133 | .json(cart); 134 | 135 | }); 136 | 137 | app.use(["/card/validate/:owner","/card/charge/:owner"],(req,res, next)=>{ 138 | const {owner} = req.params; 139 | const card = database.cards.find(card=>card.owner === owner); 140 | if (!card){ 141 | res.status(500).send({error:`No card is available for user ${owner}`}); 142 | } 143 | req.card = card; 144 | next(); 145 | }); 146 | 147 | app.get("/card/validate/:owner",(req,res)=>{ 148 | const {card} = req; 149 | res 150 | .status(200) 151 | .json({validated:true}); 152 | }); 153 | 154 | app.get("/card/charge/:owner",(req,res)=>{ 155 | const {card, cart} = req; 156 | const { owner } = req.params; 157 | const country = database.users.find(user=>user.id === owner).country; 158 | const total = cart.items.reduce((total,{quantity,id})=>{ 159 | const item = database.items.find(item=>item.id === id); 160 | const symbol = country === "CAD" ? "cad" : "usd"; 161 | const baseValue = item[symbol]; 162 | total += baseValue * quantity; 163 | return total; 164 | },0); 165 | 166 | if (card.availableFunds <= total) { 167 | return res 168 | .status(402) 169 | .json({success:false}); 170 | } 171 | 172 | card.availableFunds -= total; 173 | res 174 | .status(201) 175 | .send({success:true}); 176 | 177 | 178 | }); 179 | 180 | 181 | 182 | 183 | app.get("/items/:ids",(req,res)=>{ 184 | const ids = req.params.ids.split(','); 185 | const items = ids.map(id=>database.items.find(item=>item.id===id)); 186 | if (items.includes(undefined)) { 187 | res 188 | .status(500) 189 | .json({error:"A specified ID had no matching item"}); 190 | } else { 191 | res 192 | .status(200) 193 | .json(items); 194 | } 195 | }); 196 | 197 | app.get("/prices/:symbol/:ids",(req,res)=>{ 198 | const ids = req.params.ids.split(','); 199 | const items = ids.map(id=>database.items.find(item=>item.id===id)); 200 | const supportedSymbols = ["CAD","USD"]; 201 | const symbol = req.params.symbol; 202 | if (!supportedSymbols.includes(symbol)) { 203 | return res 204 | .status(403) 205 | .json({ 206 | error:"The currency symbol provided is inaccurate, see list of supported currencies", 207 | supportedSymbols 208 | }) 209 | } 210 | 211 | 212 | if (items.includes(undefined)) { 213 | return res 214 | .status(500) 215 | .json({error:"A specified ID had no matching item"}); 216 | } else { 217 | res 218 | .status(200) 219 | .json(items.map(item=>({ 220 | id: item.id, 221 | symbol, 222 | price:symbol === "USD" ? item.usd : item.cad 223 | }))); 224 | } 225 | }); 226 | 227 | app.get('/shipping/:items',(req,res)=>{ 228 | const ids = req.params.items.split(','); 229 | let total = 0; 230 | ids.forEach(id=>{ 231 | const item = database.items.find(item=>item.id === id); 232 | if (item.weight === 0) { 233 | total += 0; 234 | } else if (item.weight < 0.5) { 235 | total += 3.5; 236 | } else { 237 | total += 8.5; 238 | } 239 | }); 240 | res 241 | .status(200) 242 | .json({ 243 | total 244 | }); 245 | }); 246 | 247 | app.get('/tax/:symbol',(req,res)=>{ 248 | const { symbol } = req.params; 249 | const taxRate = database.taxRates.find(rate=>rate.symbol === symbol); 250 | if (!taxRate) { 251 | return res 252 | .status(500) 253 | .json({ 254 | symbol, 255 | error:"No tax rate info for symbol " + symbol 256 | }); 257 | } 258 | 259 | res 260 | .status(200) 261 | .json({ 262 | rate:taxRate.rate 263 | }) 264 | 265 | }); 266 | 267 | app.listen(port,()=>{ 268 | console.log(`Redux Saga Cart backend server is listening on ${port}`) 269 | }); 270 | }); 271 | app.use(cors()); 272 | 273 | --------------------------------------------------------------------------------