├── .gitignore ├── package.json ├── index.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "async-race-api", 3 | "version": "1.0.0", 4 | "description": "Fun with promises", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node index.js" 8 | }, 9 | "author": "mikhama", 10 | "license": "MIT", 11 | "devDependencies": { 12 | "json-server": "^0.16.3" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const jsonServer = require('json-server'); 2 | 3 | const db = { 4 | garage: [ 5 | { 6 | "name": "Tesla", 7 | "color": "#e6e6fa", 8 | "id": 1, 9 | }, 10 | { 11 | "name": "BMW", 12 | "color": "#fede00", 13 | "id": 2, 14 | }, 15 | { 16 | "name": "Mersedes", 17 | "color": "#6c779f", 18 | "id": 3, 19 | }, 20 | { 21 | "name": "Ford", 22 | "color": "#ef3c40", 23 | "id": 4, 24 | }, 25 | ], 26 | winners: [ 27 | { 28 | id: 1, 29 | wins: 1, 30 | time: 10, 31 | } 32 | ] 33 | }; 34 | 35 | const server = jsonServer.create(); 36 | const router = jsonServer.router(db); 37 | const middlewares = jsonServer.defaults(); 38 | 39 | const PORT = process.env.PORT || 3000; 40 | 41 | const state = { velocity: {}, blocked: {} }; 42 | 43 | server.use(middlewares); 44 | 45 | const STATUS = { 46 | STARTED: 'started', 47 | STOPPED: 'stopped', 48 | DRIVE: 'drive', 49 | }; 50 | 51 | server.patch('/engine', (req, res) => { 52 | const { id, status } = req.query; 53 | 54 | if (!id || Number.isNaN(+id) || +id <= 0) { 55 | return res.status(400).send('Required parameter "id" is missing. Should be a positive number'); 56 | } 57 | 58 | if (!status || !/^(started)|(stopped)|(drive)$/.test(status)) { 59 | return res.status(400).send(`Wrong parameter "status". Expected: "started", "stopped" or "drive". Received: "${status}"`); 60 | } 61 | 62 | if (!db.garage.find(car => car.id === +id)) { 63 | return res.status(404).send('Car with such id was not found in the garage.') 64 | } 65 | 66 | const distance = 500000; 67 | 68 | if (status === STATUS.DRIVE) { 69 | if (state.blocked[id]) { 70 | return res.status(429).send('Drive already in progress. You can\'t run drive for the same car twice while it\'s not stopped.'); 71 | } 72 | 73 | const velocity = state.velocity[id]; 74 | 75 | if (!velocity) { 76 | return res.status(404).send('Engine parameters for car with such id was not found in the garage. Have you tried to set engine status to "started" before?'); 77 | } 78 | 79 | 80 | state.blocked[id] = true; 81 | 82 | const x = Math.round(distance / velocity); 83 | 84 | delete state.velocity[id]; 85 | 86 | if (new Date().getMilliseconds() % 3 === 0) { 87 | setTimeout(() => { 88 | delete state.blocked[id]; 89 | res.header('Content-Type', 'application/json').status(500).send('Car has been stopped suddenly. It\'s engine was broken down.'); 90 | }, Math.random() * x ^ 0); 91 | } else { 92 | setTimeout(() => { 93 | delete state.blocked[id]; 94 | res.header('Content-Type', 'application/json').status(200).send(JSON.stringify({ success: true })); 95 | }, x); 96 | } 97 | } else { 98 | const x = req.query.speed ? +req.query.speed : Math.random() * 2000 ^ 0; 99 | 100 | const velocity = status === STATUS.STARTED ? Math.max(50, Math.random() * 200 ^ 0) : 0; 101 | 102 | if (velocity) { 103 | state.velocity[id] = velocity; 104 | } else { 105 | delete state.velocity[id]; 106 | delete state.blocked[id]; 107 | } 108 | 109 | setTimeout(() => res.header('Content-Type', 'application/json').status(200).send(JSON.stringify({ velocity, distance })), x); 110 | } 111 | }); 112 | 113 | server.use(router); 114 | server.listen(PORT, () => { 115 | console.log('Server is running on port', PORT); 116 | }); 117 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # async-race-api 2 | Api for Rolling Scopes School task "Async Race". 3 | 4 | ## Setup and Running 5 | 6 | - Use `node 14.x` or higher. 7 | - Clone this repo: `$ git clone https://github.com/mikhama/async-race-api.git`. 8 | - Go to downloaded folder: `$ cd async-race-api`. 9 | - Install dependencies: `$ npm install`. 10 | - Start server: `$ npm start`. 11 | - Now you can send requests to the address: `http://127.0.0.1:3000`. 12 | 13 | ## Usage 14 | 15 | - **Garage** 16 | - [Get Cars](https://github.com/mikhama/async-race-api#get-cars) 17 | - [Get Car](https://github.com/mikhama/async-race-api#get-car) 18 | - [Create Car](https://github.com/mikhama/async-race-api#create-car) 19 | - [Delete Car](https://github.com/mikhama/async-race-api#delete-car) 20 | - [Update Car](https://github.com/mikhama/async-race-api#update-car) 21 | - **Engine** 22 | - [Start / Stop Car's Engine](https://github.com/mikhama/async-race-api#start--stop-cars-engine) 23 | - [Switch Car's Engine to Drive Mode](https://github.com/mikhama/async-race-api#switch-cars-engine-to-drive-mode) 24 | - **Winners** 25 | - [Get Winners](https://github.com/mikhama/async-race-api#get-winners) 26 | - [Get Winner](https://github.com/mikhama/async-race-api#get-winner) 27 | - [Create Winner](https://github.com/mikhama/async-race-api#create-winner) 28 | - [Delete Winner](https://github.com/mikhama/async-race-api#delete-winner) 29 | - [Update Winner](https://github.com/mikhama/async-race-api#update-winner) 30 | 31 | **Get Cars** 32 | ---- 33 | Returns json data about cars in a garage. 34 | 35 |
36 | 37 | * **URL** 38 | 39 | /garage 40 | 41 | * **Method:** 42 | 43 | `GET` 44 | 45 | * **Headers:** 46 | 47 | None 48 | 49 | * **URL Params** 50 | 51 | None 52 | 53 | * **Query Params** 54 | 55 | **Optional:** 56 | 57 | `_page=[integer]` 58 | 59 | `_limit=[integer]` 60 | 61 | If `_limit` param is passed api returns a header `X-Total-Count` that countains total number of records. 62 | 63 | * **Data Params** 64 | 65 | None 66 | 67 | * **Success Response:** 68 | 69 | * **Code:** 200 OK
70 | **Content:** 71 | ```json 72 | [ 73 | { 74 | "name": "Tesla", 75 | "color": "#e6e6fa", 76 | "id": 1 77 | } 78 | ] 79 | ``` 80 | **Headers:** 81 | ``` 82 | "X-Total-Count": "4" 83 | ``` 84 | 85 | * **Error Response:** 86 | 87 | None 88 | 89 | * **Notes:** 90 | 91 | None 92 | 93 |
94 | 95 | **Get Car** 96 | ---- 97 | Returns json data about specified car. 98 | 99 |
100 | 101 | * **URL** 102 | 103 | /garage/:id 104 | 105 | * **Method:** 106 | 107 | `GET` 108 | 109 | * **Headers:** 110 | 111 | None 112 | 113 | * **URL Params** 114 | 115 | **Required:** 116 | 117 | `id=[integer]` 118 | 119 | * **Query Params** 120 | 121 | None 122 | 123 | * **Data Params** 124 | 125 | None 126 | 127 | * **Success Response:** 128 | 129 | * **Code:** 200 OK
130 | **Content:** 131 | ```json 132 | { 133 | "name": "Tesla", 134 | "color": "#e6e6fa", 135 | "id": 1 136 | } 137 | ``` 138 | 139 | * **Error Response:** 140 | 141 | * **Code:** 404 NOT FOUND
142 | **Content:** 143 | ```json 144 | {} 145 | ``` 146 | 147 | * **Notes:** 148 | 149 | None 150 | 151 |
152 | 153 | **Create Car** 154 | ---- 155 | Creates a new car in a garage. 156 | 157 |
158 | 159 | * **URL** 160 | 161 | /garage 162 | 163 | * **Method:** 164 | 165 | `POST` 166 | 167 | * **Headers:** 168 | 169 | `'Content-Type': 'application/json'` 170 | 171 | * **URL Params** 172 | 173 | None 174 | 175 | * **Query Params** 176 | 177 | None 178 | 179 | * **Data Params** 180 | 181 | ```typescript 182 | { 183 | name: string, 184 | color: string 185 | } 186 | ``` 187 | 188 | * **Success Response:** 189 | 190 | * **Code:** 201 CREATED
191 | **Content:** 192 | ```json 193 | { 194 | "name": "New Red Car", 195 | "color": "#ff0000", 196 | "id": 10 197 | } 198 | ``` 199 | 200 | * **Error Response:** 201 | 202 | None 203 | 204 | * **Notes:** 205 | 206 | None 207 | 208 |
209 | 210 | 211 | **Delete Car** 212 | ---- 213 | Delete specified car from a garage 214 | 215 |
216 | 217 | * **URL** 218 | 219 | /garage/:id 220 | 221 | * **Method:** 222 | 223 | `DELETE` 224 | 225 | * **Headers:** 226 | 227 | None 228 | 229 | * **URL Params** 230 | 231 | **Required:** 232 | 233 | `id=[integer]` 234 | 235 | * **Query Params** 236 | 237 | None 238 | 239 | * **Data Params** 240 | 241 | None 242 | 243 | * **Success Response:** 244 | 245 | * **Code:** 200 OK
246 | **Content:** 247 | ```json 248 | {} 249 | ``` 250 | 251 | * **Error Response:** 252 | 253 | * **Code:** 404 NOT FOUND
254 | **Content:** 255 | ```json 256 | {} 257 | ``` 258 | 259 | * **Notes:** 260 | 261 | None 262 | 263 |
264 | 265 | **Update Car** 266 | ---- 267 | Updates attributes of specified car. 268 | 269 |
270 | 271 | * **URL** 272 | 273 | /garage/:id 274 | 275 | * **Method:** 276 | 277 | `PUT` 278 | 279 | * **Headers:** 280 | 281 | `'Content-Type': 'application/json'` 282 | 283 | * **URL Params** 284 | 285 | **Required:** 286 | 287 | `id=[integer]` 288 | 289 | * **Query Params** 290 | 291 | None 292 | 293 | * **Data Params** 294 | 295 | ```typescript 296 | { 297 | name: string, 298 | color: string 299 | } 300 | ``` 301 | 302 | * **Success Response:** 303 | 304 | * **Code:** 200 OK
305 | **Content:** 306 | ```json 307 | { 308 | "name": "Car with new name", 309 | "color": "#ff00ff", 310 | "id": 2 311 | } 312 | ``` 313 | 314 | * **Error Response:** 315 | 316 | * **Code:** 404 NOT FOUND
317 | **Content:** 318 | ```json 319 | {} 320 | ``` 321 | 322 | * **Notes:** 323 | 324 | None 325 | 326 |
327 | 328 | **Start / Stop Car's Engine** 329 | ---- 330 | Starts or stops engine of specified car, and returns it's actual velocity and distance. 331 | 332 |
333 | 334 | * **URL** 335 | 336 | /engine 337 | 338 | * **Method:** 339 | 340 | `PATCH` 341 | 342 | * **Headers:** 343 | 344 | None 345 | 346 | * **URL Params** 347 | 348 | None 349 | 350 | * **Query Params** 351 | 352 | **Required:** 353 | 354 | `id=[integer]` 355 | 356 | `status=['started'|'stopped']` 357 | 358 | * **Data Params** 359 | 360 | None 361 | 362 | * **Success Response:** 363 | 364 | * **Code:** 200 OK
365 | **Content:** 366 | ```json 367 | { 368 | "velocity": 64, 369 | "distance": 500000 370 | } 371 | ``` 372 | 373 | * **Error Response:** 374 | 375 | * **Code:** 400 BAD REQUEST
376 | **Content:** 377 | 378 | Wrong parameters: "id" should be any positive number, "status" should be "started", "stopped" or "drive" 379 | 380 | OR 381 | 382 | * **Code:** 404 NOT FOUND
383 | **Content:** 384 | 385 | Car with such id was not found in the garage. 386 | 387 | * **Notes:** 388 | 389 | None 390 | 391 |
392 | 393 | **Switch Car's Engine to Drive Mode** 394 | ---- 395 | Switches engine of specified car to drive mode and finishes with success message or fails with 500 error. 396 | 397 |
398 | 399 | * **URL** 400 | 401 | /engine 402 | 403 | * **Method:** 404 | 405 | `PATCH` 406 | 407 | * **Headers:** 408 | 409 | None 410 | 411 | * **URL Params** 412 | 413 | None 414 | 415 | * **Query Params** 416 | 417 | **Required:** 418 | 419 | `id=[integer]` 420 | 421 | `status=['drive']` 422 | 423 | * **Data Params** 424 | 425 | None 426 | 427 | * **Success Response:** 428 | 429 | * **Code:** 200 OK
430 | **Content:** 431 | ```json 432 | { 433 | "success": true 434 | } 435 | ``` 436 | 437 | * **Error Response:** 438 | 439 | * **Code:** 400 BAD REQUEST
440 | **Content:** 441 | 442 | Wrong parameters: "id" should be any positive number, "status" should be "started", "stopped" or "drive" 443 | 444 | OR 445 | 446 | * **Code:** 404 NOT FOUND
447 | **Content:** 448 | 449 | Engine parameters for car with such id was not found in the garage. Have you tried to set engine status to "started" before? 450 | 451 | OR 452 | 453 | * **Code:** 429 TOO MANY REQUESTS
454 | **Content:** 455 | 456 | Drive already in progress. You can't run drive for the same car twice while it's not stopped. 457 | 458 | OR 459 | 460 | * **Code:** 500 INTERNAL SERVER ERROR
461 | **Content:** 462 | 463 | Car has been stopped suddenly. It's engine was broken down. 464 | 465 | * **Notes:** 466 | 467 | - Before using this request you need to switch engine status to the 'started' status first. 468 | - Time when response will finish can be calculated using response from making engine 'started'. 469 | - Engine may fall randomly and at random time at the whole distance. 470 | 471 |
472 | 473 | **Get Winners** 474 | ---- 475 | Returns json data about winners. 476 | 477 |
478 | 479 | * **URL** 480 | 481 | /winners 482 | 483 | * **Method:** 484 | 485 | `GET` 486 | 487 | * **Headers:** 488 | 489 | None 490 | 491 | * **URL Params** 492 | 493 | None 494 | 495 | * **Query Params** 496 | 497 | **Optional:** 498 | 499 | `_page=[integer]` 500 | 501 | `_limit=[integer]` 502 | 503 | `_sort=['id'|'wins'|'time']` 504 | 505 | `_order=['ASC'|'DESC']` 506 | 507 | If `_limit` param is passed api returns a header `X-Total-Count` that countains total number of records. 508 | 509 | * **Data Params** 510 | 511 | None 512 | 513 | * **Success Response:** 514 | 515 | * **Code:** 200 OK
516 | **Content:** 517 | ```json 518 | [ 519 | { 520 | "id": 16, 521 | "wins": 1, 522 | "time": 2.92 523 | } 524 | ] 525 | ``` 526 | **Headers:** 527 | ``` 528 | "X-Total-Count": "4" 529 | ``` 530 | 531 | * **Error Response:** 532 | 533 | None 534 | 535 | * **Notes:** 536 | 537 | None 538 | 539 |
540 | 541 | **Get Winner** 542 | ---- 543 | Returns json data about the specified winner. 544 | 545 |
546 | 547 | * **URL** 548 | 549 | /winners/:id 550 | 551 | * **Method:** 552 | 553 | `GET` 554 | 555 | * **Headers:** 556 | 557 | None 558 | 559 | * **URL Params** 560 | 561 | **Required:** 562 | 563 | `id=[integer]` 564 | 565 | * **Query Params** 566 | 567 | None 568 | 569 | * **Data Params** 570 | 571 | None 572 | 573 | * **Success Response:** 574 | 575 | * **Code:** 200 OK
576 | **Content:** 577 | ```json 578 | { 579 | "id": 1, 580 | "wins": 1, 581 | "time": 10 582 | } 583 | ``` 584 | 585 | * **Error Response:** 586 | 587 | * **Code:** 404 NOT FOUND
588 | **Content:** 589 | ```json 590 | {} 591 | ``` 592 | 593 | * **Notes:** 594 | 595 | None 596 | 597 |
598 | 599 | **Create Winner** 600 | ---- 601 | Creates a new records in the winners table. 602 | 603 |
604 | 605 | * **URL** 606 | 607 | /winners 608 | 609 | * **Method:** 610 | 611 | `POST` 612 | 613 | * **Headers:** 614 | 615 | `'Content-Type': 'application/json'` 616 | 617 | * **URL Params** 618 | 619 | None 620 | 621 | * **Query Params** 622 | 623 | None 624 | 625 | * **Data Params** 626 | 627 | ```typescript 628 | { 629 | id: number, 630 | wins: number, 631 | time: number 632 | } 633 | ``` 634 | 635 | * **Success Response:** 636 | 637 | * **Code:** 201 CREATED
638 | **Content:** 639 | ```json 640 | { 641 | "id": 109, 642 | "wins": 1, 643 | "time": 10 644 | } 645 | ``` 646 | 647 | * **Error Response:** 648 | 649 | * **Code:** 500 INTERNAL SERVER ERROR
650 | **Content:** 651 | 652 | Error: Insert failed, duplicate id 653 | 654 | * **Notes:** 655 | 656 | None 657 | 658 |
659 | 660 | **Delete Winner** 661 | ---- 662 | Delete the specified car from the winners table. 663 | 664 |
665 | 666 | * **URL** 667 | 668 | /winners/:id 669 | 670 | * **Method:** 671 | 672 | `DELETE` 673 | 674 | * **Headers:** 675 | 676 | None 677 | 678 | * **URL Params** 679 | 680 | **Required:** 681 | 682 | `id=[integer]` 683 | 684 | * **Query Params** 685 | 686 | None 687 | 688 | * **Data Params** 689 | 690 | None 691 | 692 | * **Success Response:** 693 | 694 | * **Code:** 200 OK
695 | **Content:** 696 | ```json 697 | {} 698 | ``` 699 | 700 | * **Error Response:** 701 | 702 | * **Code:** 404 NOT FOUND
703 | **Content:** 704 | ```json 705 | {} 706 | ``` 707 | 708 | * **Notes:** 709 | 710 | None 711 | 712 |
713 | 714 | **Update Winner** 715 | ---- 716 | Updates attributes of the specified winner. 717 | 718 |
719 | 720 | * **URL** 721 | 722 | /winners/:id 723 | 724 | * **Method:** 725 | 726 | `PUT` 727 | 728 | * **Headers:** 729 | 730 | `'Content-Type': 'application/json'` 731 | 732 | * **URL Params** 733 | 734 | **Required:** 735 | 736 | `id=[integer]` 737 | 738 | * **Query Params** 739 | 740 | None 741 | 742 | * **Data Params** 743 | 744 | ```typescript 745 | { 746 | wins: number, 747 | time: number 748 | } 749 | ``` 750 | 751 | * **Success Response:** 752 | 753 | * **Code:** 200 OK
754 | **Content:** 755 | ```json 756 | { 757 | "wins": 2, 758 | "time": 11, 759 | "id": 16 760 | } 761 | ``` 762 | 763 | * **Error Response:** 764 | 765 | * **Code:** 404 NOT FOUND
766 | **Content:** 767 | ```json 768 | {} 769 | ``` 770 | 771 | * **Notes:** 772 | 773 | None 774 | 775 |
776 | --------------------------------------------------------------------------------