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