From 449a2d38f13c5aaaa885f31d23c20b1cdae00121 Mon Sep 17 00:00:00 2001 From: AntonV1211 Date: Sun, 15 Jun 2025 21:31:12 +0700 Subject: [PATCH 1/6] hw-9 --- materials/mock.js | 671 ------------------ package.json | 5 +- simple_api/README.md | 21 + simple_api/api/index.js | 97 +++ .../api/mock.js | 25 +- simple_api/api/utils.js | 16 + simple_api/package-lock.json | 577 +++++++++++++++ simple_api/package.json | 13 + simple_api/pnpm-lock.yaml | 455 ++++++++++++ simple_api/server.js | 27 + simple_api/yarn.lock | 357 ++++++++++ src/components/dishPage/DishPage.jsx | 17 +- src/components/menu/MenuTab.jsx | 20 +- .../restaurant/RestaurantDetailsLayout.jsx | 14 +- src/components/restaurant/RestaurantTabs.jsx | 12 +- src/components/reviews/ReviewsTab.jsx | 37 +- src/redux/entities/dishes/dishesSlice.js | 73 +- .../entities/restaurants/restaurantsSlice.js | 65 +- src/redux/entities/reviews/reviewsSlice.js | 61 +- src/redux/entities/users/usersSlice.js | 50 +- src/redux/request_status/requestStatus.js | 6 + 21 files changed, 1861 insertions(+), 758 deletions(-) delete mode 100644 materials/mock.js create mode 100644 simple_api/README.md create mode 100644 simple_api/api/index.js rename materials/normalized-mock.js => simple_api/api/mock.js (78%) create mode 100644 simple_api/api/utils.js create mode 100644 simple_api/package-lock.json create mode 100644 simple_api/package.json create mode 100644 simple_api/pnpm-lock.yaml create mode 100644 simple_api/server.js create mode 100644 simple_api/yarn.lock create mode 100644 src/redux/request_status/requestStatus.js diff --git a/materials/mock.js b/materials/mock.js deleted file mode 100644 index 3507bd3..0000000 --- a/materials/mock.js +++ /dev/null @@ -1,671 +0,0 @@ -export const restaurants = [ - { - id: "a757a0e9-03c1-4a2a-b384-8ac21dbe2fb2", - name: "Dishoom", - menu: [ - { - id: "d75f762a-eadd-49be-8918-ed0daa8dd024", - name: "Chicken tikka masala", - price: 12, - ingredients: ["chicken", "rice"], - }, - { - id: "c3cb8f92-a2ed-4716-92a1-b6ea813e9049", - name: "Naan", - price: 3, - ingredients: ["bread"], - }, - { - id: "bd129641-c0eb-432b-84b6-8b81d2930358", - name: "Samosa", - price: 8, - ingredients: ["chicken", "bread"], - }, - { - id: "1a2b3c4d-1111-2222-3333-444455556666", - name: "Paneer Butter Masala", - price: 11, - ingredients: ["paneer", "tomato", "cream", "spices"], - }, - { - id: "1a2b3c4d-7777-8888-9999-000011112222", - name: "Lassi", - price: 4, - ingredients: ["yogurt", "sugar", "cardamom"], - }, - { - id: "1a2b3c4d-aaaa-bbbb-cccc-ddddeeeeffff", - name: "Biryani", - price: 13, - ingredients: ["rice", "chicken", "spices", "onion"], - }, - { - id: "1a2b3c4d-1234-5678-90ab-cdef12345678", - name: "Aloo Gobi", - price: 9, - ingredients: ["potato", "cauliflower", "spices"], - }, - { - id: "dishoom-001-unique-uuid", - name: "Tandoori Chicken", - price: 14, - ingredients: ["chicken", "yogurt", "spices", "lemon"], - }, - { - id: "dishoom-002-unique-uuid", - name: "Dal Makhani", - price: 10, - ingredients: ["lentils", "cream", "spices", "butter"], - }, - { - id: "dishoom-003-unique-uuid", - name: "Raita", - price: 4, - ingredients: ["yogurt", "cucumber", "spices"], - }, - { - id: "dishoom-004-unique-uuid", - name: "Garlic Naan", - price: 4, - ingredients: ["bread", "garlic", "butter"], - }, - { - id: "dishoom-005-unique-uuid", - name: "Mango Lassi", - price: 5, - ingredients: ["yogurt", "mango", "sugar"], - }, - { - id: "dishoom-006-unique-uuid", - name: "Vegetable Pakora", - price: 7, - ingredients: ["vegetables", "chickpea flour", "spices"], - }, - { - id: "dishoom-007-unique-uuid", - name: "Butter Chicken", - price: 13, - ingredients: ["chicken", "butter", "tomato", "cream", "spices"], - }, - { - id: "dishoom-008-unique-uuid", - name: "Jeera Rice", - price: 5, - ingredients: ["rice", "cumin", "butter"], - }, - { - id: "dishoom-009-unique-uuid", - name: "Masala Chai", - price: 3, - ingredients: ["tea", "milk", "spices", "sugar"], - }, - { - id: "dishoom-010-unique-uuid", - name: "Gulab Jamun", - price: 6, - ingredients: ["milk", "sugar", "rose water"], - }, - { - id: "dishoom-011-unique-uuid", - name: "Chana Masala", - price: 8, - ingredients: ["chickpeas", "tomato", "onion", "spices"], - }, - { - id: "dishoom-012-unique-uuid", - name: "Fish Curry", - price: 15, - ingredients: ["fish", "coconut milk", "spices", "tomato"], - }, - { - id: "dishoom-013-unique-uuid", - name: "Bhindi Fry", - price: 9, - ingredients: ["okra", "onion", "spices"], - }, - { - id: "dishoom-014-unique-uuid", - name: "Egg Curry", - price: 10, - ingredients: ["egg", "tomato", "onion", "spices"], - }, - { - id: "dishoom-015-unique-uuid", - name: "Prawn Masala", - price: 16, - ingredients: ["prawn", "tomato", "onion", "spices"], - }, - { - id: "dishoom-016-unique-uuid", - name: "Palak Paneer", - price: 11, - ingredients: ["paneer", "spinach", "cream", "spices"], - }, - { - id: "dishoom-017-unique-uuid", - name: "Methi Paratha", - price: 5, - ingredients: ["bread", "fenugreek", "spices"], - }, - { - id: "dishoom-018-unique-uuid", - name: "Kheer", - price: 6, - ingredients: ["rice", "milk", "sugar", "cardamom"], - }, - { - id: "dishoom-019-unique-uuid", - name: "Onion Bhaji", - price: 7, - ingredients: ["onion", "chickpea flour", "spices"], - }, - { - id: "dishoom-020-unique-uuid", - name: "Malai Kofta", - price: 12, - ingredients: ["potato", "paneer", "cream", "spices"], - }, - { - id: "dishoom-021-unique-uuid", - name: "Shahi Paneer", - price: 12, - ingredients: ["paneer", "cream", "tomato", "spices"], - }, - { - id: "dishoom-022-unique-uuid", - name: "Chicken Korma", - price: 14, - ingredients: ["chicken", "cream", "cashew", "spices"], - }, - { - id: "dishoom-023-unique-uuid", - name: "Vegetable Biryani", - price: 11, - ingredients: ["rice", "vegetables", "spices"], - }, - { - id: "dishoom-024-unique-uuid", - name: "Lamb Rogan Josh", - price: 15, - ingredients: ["lamb", "yogurt", "spices", "tomato"], - }, - { - id: "dishoom-025-unique-uuid", - name: "Paneer Tikka", - price: 13, - ingredients: ["paneer", "yogurt", "spices"], - }, - { - id: "dishoom-026-unique-uuid", - name: "Aloo Tikki", - price: 7, - ingredients: ["potato", "spices", "bread crumbs"], - }, - { - id: "dishoom-027-unique-uuid", - name: "Chicken Seekh Kebab", - price: 14, - ingredients: ["chicken", "spices", "onion"], - }, - { - id: "dishoom-028-unique-uuid", - name: "Baingan Bharta", - price: 10, - ingredients: ["eggplant", "tomato", "onion", "spices"], - }, - { - id: "dishoom-029-unique-uuid", - name: "Dhokla", - price: 6, - ingredients: ["chickpea flour", "yogurt", "spices"], - }, - { - id: "dishoom-030-unique-uuid", - name: "Moong Dal Halwa", - price: 8, - ingredients: ["moong dal", "ghee", "sugar", "cardamom"], - }, - { - id: "dishoom-031-unique-uuid", - name: "Chicken Vindaloo", - price: 15, - ingredients: ["chicken", "vinegar", "spices", "potato"], - }, - { - id: "dishoom-032-unique-uuid", - name: "Rajma", - price: 9, - ingredients: ["kidney beans", "tomato", "spices"], - }, - { - id: "dishoom-033-unique-uuid", - name: "Pav Bhaji", - price: 8, - ingredients: ["vegetables", "potato", "bread", "spices"], - }, - { - id: "dishoom-034-unique-uuid", - name: "Dahi Puri", - price: 7, - ingredients: ["puri", "yogurt", "potato", "spices"], - }, - { - id: "dishoom-035-unique-uuid", - name: "Chicken 65", - price: 13, - ingredients: ["chicken", "spices", "yogurt"], - }, - { - id: "dishoom-036-unique-uuid", - name: "Paneer Bhurji", - price: 11, - ingredients: ["paneer", "onion", "tomato", "spices"], - }, - { - id: "dishoom-037-unique-uuid", - name: "Gajar Halwa", - price: 8, - ingredients: ["carrot", "milk", "sugar", "cardamom"], - }, - { - id: "dishoom-038-unique-uuid", - name: "Chicken Chettinad", - price: 15, - ingredients: ["chicken", "coconut", "spices"], - }, - { - id: "dishoom-039-unique-uuid", - name: "Pani Puri", - price: 6, - ingredients: ["puri", "spiced water", "potato"], - }, - { - id: "dishoom-040-unique-uuid", - name: "Rasgulla", - price: 7, - ingredients: ["milk", "sugar", "rose water"], - }, - { - id: "dishoom-041-unique-uuid", - name: "Chicken Do Pyaza", - price: 14, - ingredients: ["chicken", "onion", "spices"], - }, - { - id: "dishoom-042-unique-uuid", - name: "Paneer Pasanda", - price: 13, - ingredients: ["paneer", "cream", "spices"], - }, - { - id: "dishoom-043-unique-uuid", - name: "Dum Aloo", - price: 10, - ingredients: ["potato", "tomato", "spices"], - }, - { - id: "dishoom-044-unique-uuid", - name: "Chicken Handi", - price: 15, - ingredients: ["chicken", "cream", "spices"], - }, - { - id: "dishoom-045-unique-uuid", - name: "Kadai Paneer", - price: 12, - ingredients: ["paneer", "bell pepper", "spices"], - }, - { - id: "dishoom-046-unique-uuid", - name: "Chicken Bhuna", - price: 14, - ingredients: ["chicken", "tomato", "spices"], - }, - { - id: "dishoom-047-unique-uuid", - name: "Moong Dal", - price: 8, - ingredients: ["moong dal", "spices", "onion"], - }, - { - id: "dishoom-048-unique-uuid", - name: "Paneer Lababdar", - price: 13, - ingredients: ["paneer", "tomato", "cream", "spices"], - }, - { - id: "dishoom-049-unique-uuid", - name: "Chicken Saagwala", - price: 15, - ingredients: ["chicken", "spinach", "spices"], - }, - { - id: "dishoom-050-unique-uuid", - name: "Aloo Matar", - price: 9, - ingredients: ["potato", "peas", "spices"], - }, - { - id: "dishoom-051-unique-uuid", - name: "Paneer Do Pyaza", - price: 12, - ingredients: ["paneer", "onion", "spices"], - }, - { - id: "dishoom-052-unique-uuid", - name: "Chicken Madras", - price: 15, - ingredients: ["chicken", "coconut", "spices"], - }, - { - id: "dishoom-053-unique-uuid", - name: "Bhatura", - price: 5, - ingredients: ["bread", "yogurt", "flour"], - }, - { - id: "dishoom-054-unique-uuid", - name: "Paneer Malai Tikka", - price: 13, - ingredients: ["paneer", "cream", "spices"], - }, - { - id: "dishoom-055-unique-uuid", - name: "Chicken Malai Tikka", - price: 14, - ingredients: ["chicken", "cream", "spices"], - }, - { - id: "dishoom-056-unique-uuid", - name: "Aloo Baingan", - price: 10, - ingredients: ["potato", "eggplant", "spices"], - }, - { - id: "dishoom-057-unique-uuid", - name: "Paneer Korma", - price: 12, - ingredients: ["paneer", "cream", "cashew", "spices"], - }, - { - id: "dishoom-058-unique-uuid", - name: "Chicken Kathi Roll", - price: 13, - ingredients: ["chicken", "bread", "spices"], - }, - { - id: "dishoom-059-unique-uuid", - name: "Paneer Kathi Roll", - price: 12, - ingredients: ["paneer", "bread", "spices"], - }, - { - id: "dishoom-060-unique-uuid", - name: "Chicken Patiala", - price: 15, - ingredients: ["chicken", "egg", "spices"], - }, - { - id: "dishoom-061-unique-uuid", - name: "Paneer Patiala", - price: 13, - ingredients: ["paneer", "egg", "spices"], - }, - { - id: "dishoom-062-unique-uuid", - name: "Chicken Kolhapuri", - price: 15, - ingredients: ["chicken", "coconut", "spices"], - }, - { - id: "dishoom-063-unique-uuid", - name: "Paneer Kolhapuri", - price: 13, - ingredients: ["paneer", "coconut", "spices"], - }, - { - id: "dishoom-064-unique-uuid", - name: "Chicken Xacuti", - price: 16, - ingredients: ["chicken", "coconut", "spices"], - }, - { - id: "dishoom-065-unique-uuid", - name: "Paneer Xacuti", - price: 14, - ingredients: ["paneer", "coconut", "spices"], - }, - { - id: "dishoom-066-unique-uuid", - name: "Chicken Hyderabadi", - price: 16, - ingredients: ["chicken", "yogurt", "spices"], - }, - { - id: "dishoom-067-unique-uuid", - name: "Paneer Hyderabadi", - price: 14, - ingredients: ["paneer", "yogurt", "spices"], - }, - { - id: "dishoom-068-unique-uuid", - name: "Chicken Angara", - price: 15, - ingredients: ["chicken", "smoked spices"], - }, - { - id: "dishoom-069-unique-uuid", - name: "Paneer Angara", - price: 13, - ingredients: ["paneer", "smoked spices"], - }, - { - id: "dishoom-070-unique-uuid", - name: "Chicken Afghani", - price: 16, - ingredients: ["chicken", "cream", "spices"], - }, - { - id: "dishoom-071-unique-uuid", - name: "Paneer Afghani", - price: 14, - ingredients: ["paneer", "cream", "spices"], - }, - { - id: "dishoom-072-unique-uuid", - name: "Chicken Tikka Roll", - price: 13, - ingredients: ["chicken", "bread", "spices"], - }, - { - id: "dishoom-073-unique-uuid", - name: "Paneer Tikka Roll", - price: 12, - ingredients: ["paneer", "bread", "spices"], - }, - { - id: "dishoom-074-unique-uuid", - name: "Chicken Frankie", - price: 13, - ingredients: ["chicken", "egg", "bread", "spices"], - }, - { - id: "dishoom-075-unique-uuid", - name: "Paneer Frankie", - price: 12, - ingredients: ["paneer", "egg", "bread", "spices"], - }, - { - id: "dishoom-076-unique-uuid", - name: "Chicken Bharta", - price: 14, - ingredients: ["chicken", "eggplant", "spices"], - }, - { - id: "dishoom-077-unique-uuid", - name: "Paneer Bharta", - price: 12, - ingredients: ["paneer", "eggplant", "spices"], - }, - { - id: "dishoom-078-unique-uuid", - name: "Chicken Jalfrezi", - price: 15, - ingredients: ["chicken", "bell pepper", "spices"], - }, - { - id: "dishoom-079-unique-uuid", - name: "Paneer Jalfrezi", - price: 13, - ingredients: ["paneer", "bell pepper", "spices"], - }, - { - id: "dishoom-080-unique-uuid", - name: "Chicken Dhansak", - price: 16, - ingredients: ["chicken", "lentils", "spices"], - }, - ], - reviews: [ - { - id: "5909796d-5030-4e36-adec-68b8f9ec2d96", - user: "Antony", - text: "Not bad", - rating: 5, - }, - { - id: "429dea85-11dd-4054-a31e-c60c92e17255", - user: "Sam", - text: "No burgers", - rating: 3, - }, - ], - }, - { - id: "bb8afbec-2fec-491f-93e9-7f13950dd80b", - name: "Homeslice", - menu: [ - { - id: "25402233-0095-49ea-9939-1e67ed89ffb9", - name: "Margarita", - price: 9, - ingredients: ["bread", "cheese", "tomatoes"], - }, - { - id: "90902233-0095-49ea-9939-1e67ed89ffb9", - name: "Chef pizza", - price: 10, - ingredients: ["bread", "cheese", "tomatoes", "chicken"], - }, - ], - reviews: [ - { - id: "53b642d7-5e86-4717-a466-0640a1dee076", - user: "Diana", - text: "Perfect Margarita", - rating: 5, - }, - { - id: "c27ab88e-375c-4e98-aa94-8a180150a797", - user: "Sam", - text: "No burgers again. But Chef Pizza is the best one", - rating: 4, - }, - { - id: "abc0c5e1-cd57-4f0a-99d9-00e6b4533b3a", - user: "Lolly", - text: "Good for lunch", - rating: 5, - }, - ], - }, - { - id: "982bfbce-c5e0-41a0-9f99-d5c20ecee49d", - name: "Fabrique", - menu: [ - { - id: "08c9ffa0-d003-4310-9e15-20978743296e", - name: "Cinnamon buns", - price: 5, - ingredients: ["bread"], - }, - { - id: "64a4967c-2080-4a99-9074-4655a4569a95", - name: "Semlor", - price: 2, - ingredients: ["bread", "cream"], - }, - { - id: "4bc8528e-26d1-46c3-a522-8e18d10c8c84", - name: "Saffron bun", - price: 4, - ingredients: ["bread"], - }, - ], - reviews: [ - { - id: "53b642d7-5e86-4717-a466-0640a1dee076", - user: "Agata", - text: "Best bakery", - rating: 5, - }, - ], - }, - { - id: "982bfbce-c5e0-41a0-9f99-d5c2gfdse49d", - name: "No reviews", - menu: [ - { - id: "08c9ffa0-d003-4310-9e15-2097asda43296e", - name: "Cinnamon buns", - price: 5, - ingredients: ["bread"], - }, - { - id: "64a4967c-2080-4a99-9074-4655vcxzx69a95", - name: "Semlor", - price: 2, - ingredients: ["bread", "cream"], - }, - { - id: "4bc8528e-26d1-46c3-a522-8e18d1bcvxzx8c84", - name: "Saffron bun", - price: 4, - ingredients: ["bread"], - }, - ], - reviews: [], - }, - { - id: "d9241927-09e1-44f3-8986-a76346869037", - name: "Flat Iron", - menu: [ - { - id: "6c02c2ce-b868-4191-b4a7-8686429f4bac", - name: "Flat Iron Steak", - price: 10, - ingredients: ["beef"], - }, - { - id: "99bb6fbb-e53b-4b7e-b9c2-23b63b77385d", - name: "Flat Iron Burger", - price: 10, - ingredients: ["bread", "beef"], - }, - ], - reviews: [ - { - id: "5db6247b-ab1c-49db-be1f-8dd27fd38b81", - user: "Sam", - text: "Finally! This place is amazing place for breakfast, lunch, dinner and supper", - rating: 5, - }, - { - id: "381b0c31-6360-43ff-80d1-581a116159d8", - user: "Rebeca", - text: "Meat here is extremely delicious", - rating: 5, - }, - ], - }, -]; diff --git a/package.json b/package.json index d07e74a..d8b80a6 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "dev": "vite", "build": "vite build", "lint": "eslint .", - "preview": "vite preview" + "preview": "vite preview", + "start-server": "node simple_api/server.js" }, "dependencies": { "@reduxjs/toolkit": "^2.8.2", @@ -30,4 +31,4 @@ "globals": "^16.0.0", "vite": "^6.3.5" } -} +} \ No newline at end of file diff --git a/simple_api/README.md b/simple_api/README.md new file mode 100644 index 0000000..574219f --- /dev/null +++ b/simple_api/README.md @@ -0,0 +1,21 @@ +# API + +## Ресторан + +1. GET /api/restaurants/ - все рестораны; +2. GET /api/restaurant/:restaurantId - ресторан по айдишки (/api/restaurant/d32n32d8huasj ); + +## Блюда + +1. GET /api/dishes?restaurantId=:restaurantId - получить блюда по айди ресторана (/api/dishes?restaurantId=d32n32d8huasj) +2. GET /api/dish/:dishId - блюдо по айдишки (/api/dish/djshfusdhfi29 ) + +## Отзывы + +1. GET /api/reviews?restaurantId=:restaurantId - получить отзывы по айди ресторана (/api/reviews?restaurantId=d32n32d8huasj) +2. POST /api/review/:restaurantId - создать отзыв по айди ресторана (/api/review/d32n32d8huasj, а в бади сам отзыв без айдишки) +3. PATCH /api/review/:reviewId - изменить отзыв по айди ресторана (/api/review/d32n32d8huasj, а в бади сам отзыв без айдишки) + +## Пользователи + +1. GET /api/users/ - все пользователи; diff --git a/simple_api/api/index.js b/simple_api/api/index.js new file mode 100644 index 0000000..9882024 --- /dev/null +++ b/simple_api/api/index.js @@ -0,0 +1,97 @@ +const router = require("express").Router(); +const { nanoid } = require("nanoid"); +const { restaurants, products, reviews, users } = require("./mock"); +const { reply, getById, updateById } = require("./utils"); + +router.get("/restaurants", (req, res, next) => { + console.log("request"); + reply(res, restaurants); +}); + +router.get("/restaurant/:restaurantId", (req, res, next) => { + const restaurantId = req.params?.restaurantId; + let restaurant; + + if (restaurantId) { + restaurant = getById(restaurants)(restaurantId); + } + + reply(res, restaurant); +}); + +router.get("/dishes", (req, res, next) => { + const { restaurantId, dishId } = req.query; + let result = products; + + if (restaurantId) { + const restaurant = getById(restaurants)(restaurantId); + if (restaurant) { + result = restaurant.menu.map(getById(result)); + } + } + + if (!restaurantId && dishId) { + result = getById(result)(dishId); + } + reply(res, result); +}); + +router.get("/dish/:dishId", (req, res, next) => { + const dishId = req.params?.dishId; + let product; + + if (dishId) { + product = getById(products)(dishId); + } + reply(res, product); +}); + +router.get("/reviews", (req, res, next) => { + const { restaurantId } = req.query; + let result = reviews; + if (restaurantId) { + const restaurant = getById(restaurants)(restaurantId); + if (restaurant) { + result = restaurant.reviews.map(getById(result)); + } + } + reply(res, result); +}); + +router.post("/review/:restaurantId", (req, res, next) => { + const body = req.body; + const restaurantId = req.params?.restaurantId; + const restaurant = restaurantId && getById(restaurants)(restaurantId); + let newReview = {}; + + if (restaurant && body) { + const newReviewId = nanoid(); + + newReview = { + ...body, + id: newReviewId, + }; + restaurant.reviews.push(newReviewId); + reviews.push(newReview); + } + + reply(res, newReview); +}); + +router.patch("/review/:reviewId", (req, res, next) => { + const body = req.body; + const reviewId = req.params?.reviewId; + let updatedReview; + + if (reviewId) { + updatedReview = updateById(reviews)(reviewId, body); + } + + reply(res, updatedReview); +}); + +router.get("/users", (req, res, next) => { + reply(res, users); +}); + +module.exports = router; diff --git a/materials/normalized-mock.js b/simple_api/api/mock.js similarity index 78% rename from materials/normalized-mock.js rename to simple_api/api/mock.js index 389bb56..ce01062 100644 --- a/materials/normalized-mock.js +++ b/simple_api/api/mock.js @@ -1,7 +1,9 @@ -export const normalizedRestaurants = [ +const normalizedRestaurants = [ { id: "a757a0e9-03c1-4a2a-b384-8ac21dbe2fb2", - name: "Dishoom", + name: "Вкусно и точка", + description: "Японская кухня", + img: "https://images.unsplash.com/photo-1504674900247-0877df9cc836?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2340&q=80", menu: [ "d75f762a-eadd-49be-8918-ed0daa8dd024", "c3cb8f92-a2ed-4716-92a1-b6ea813e9049", @@ -15,6 +17,8 @@ export const normalizedRestaurants = [ { id: "bb8afbec-2fec-491f-93e9-7f13950dd80b", name: "Homeslice", + description: "Итальянская кухня", + img: "https://images.unsplash.com/photo-1455619452474-d2be8b1e70cd?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2370&q=80", menu: [ "25402233-0095-49ea-9939-1e67ed89ffb9", "90902233-0095-49ea-9939-1e67ed89ffb9", @@ -28,6 +32,8 @@ export const normalizedRestaurants = [ { id: "982bfbce-c5e0-41a0-9f99-d5c20ecee49d", name: "Fabrique", + description: "Русская кухня", + img: "https://images.unsplash.com/photo-1562565652-a0d8f0c59eb4?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=3264&q=80", menu: [ "08c9ffa0-d003-4310-9e15-20978743296e", "64a4967c-2080-4a99-9074-4655a4569a95", @@ -38,6 +44,8 @@ export const normalizedRestaurants = [ { id: "d9241927-09e1-44f3-8986-a76346869037", name: "Flat Iron", + description: "Грузинская кухня", + img: "https://images.unsplash.com/photo-1559314809-0d155014e29e?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2370&q=80", menu: [ "6c02c2ce-b868-4191-b4a7-8686429f4bac", "99bb6fbb-e53b-4b7e-b9c2-23b63b77385d", @@ -49,7 +57,7 @@ export const normalizedRestaurants = [ }, ]; -export const normalizedDishes = [ +const normalizedProducts = [ { id: "d75f762a-eadd-49be-8918-ed0daa8dd024", name: "Chicken tikka masala", @@ -112,7 +120,7 @@ export const normalizedDishes = [ }, ]; -export const normalizedReviews = [ +const normalizedReviews = [ { id: "5909796d-5030-4e36-adec-68b8f9ec2d96", userId: "a304959a-76c0-4b34-954a-b38dbf310360", @@ -163,7 +171,7 @@ export const normalizedReviews = [ }, ]; -export const normalizedUsers = [ +const normalizedUsers = [ { id: "a304959a-76c0-4b34-954a-b38dbf310360", name: "Antony", @@ -189,3 +197,10 @@ export const normalizedUsers = [ name: "Sam", }, ]; + +module.exports = { + products: normalizedProducts, + restaurants: normalizedRestaurants, + reviews: normalizedReviews, + users: normalizedUsers, +}; diff --git a/simple_api/api/utils.js b/simple_api/api/utils.js new file mode 100644 index 0000000..758965c --- /dev/null +++ b/simple_api/api/utils.js @@ -0,0 +1,16 @@ +const reply = (res, body, timeout = 1000, status = 200) => + setTimeout(() => { + res.status(status).json(body); + }, timeout); + +const getById = (entities) => (id) => + entities.find((entity) => entity.id === id); + +const updateById = (entities) => (id, data) => { + const index = entities.findIndex((entity) => entity.id === id); + entities[index] = { ...entities[index], ...data }; + + return entities[index]; +}; + +module.exports = { reply, getById, updateById }; diff --git a/simple_api/package-lock.json b/simple_api/package-lock.json new file mode 100644 index 0000000..19c8e13 --- /dev/null +++ b/simple_api/package-lock.json @@ -0,0 +1,577 @@ +{ + "name": "simple_api", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "simple_api", + "version": "0.0.1", + "dependencies": { + "body-parser": "^1.19.0", + "express": "^4.17.1", + "nanoid": "^3.3.6" + } + }, + "node_modules/accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.1.tgz", + "integrity": "sha512-8ljfQi5eBk8EJfECMrgqNGWPEY5jWP+1IzkzkGdFFEwFQZZyaZ21UqdaHktgiMlH0xLHqIFtE/u2OYE5dOtViA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.1", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.8.1", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.9.6", + "raw-body": "2.4.2", + "type-is": "~1.6.18" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/bytes": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.1.tgz", + "integrity": "sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw= sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= sha512-3NdhDuEXnfun/z7x9GOElY49LoqVHoGScmOKwmxhsS8N5Y+Z8KyPPDnaSzqWgYt/ji4mqwfTS34Htrk0zPIXVg==", + "license": "MIT" + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.2.tgz", + "integrity": "sha512-oxlxJxcQlYwqPWKVJJtvQiwHgosH/LrLSPA+H4UxpyvSS6jC5aH+5MoHFM+KABgTOt0APue4w66Ha8jCUo9QGg==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.7", + "array-flatten": "1.1.1", + "body-parser": "1.19.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.4.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.9.6", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.17.2", + "serve-static": "1.14.2", + "setprototypeof": "1.2.0", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", + "license": "MIT" + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.51.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", + "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.34", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", + "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", + "license": "MIT", + "dependencies": { + "mime-db": "1.51.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", + "license": "MIT" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.9.6", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.6.tgz", + "integrity": "sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.2.tgz", + "integrity": "sha512-RPMAFUJP19WIet/99ngh6Iv8fzAbqum4Li7AD6DtGaW2RpMB/11xDoalPiJMTbu6I3hkbMVkATvZrqb9EEqeeQ==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.1", + "http-errors": "1.8.1", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/send": { + "version": "0.17.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", + "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "1.8.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz", + "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==", + "license": "MIT", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + } + } +} diff --git a/simple_api/package.json b/simple_api/package.json new file mode 100644 index 0000000..a2cfc54 --- /dev/null +++ b/simple_api/package.json @@ -0,0 +1,13 @@ +{ + "name": "simple_api", + "version": "0.0.1", + "description": "core", + "scripts": { + "start": "supervisor server.js" + }, + "dependencies": { + "body-parser": "^1.19.0", + "express": "^4.17.1", + "nanoid": "^3.3.6" + } +} diff --git a/simple_api/pnpm-lock.yaml b/simple_api/pnpm-lock.yaml new file mode 100644 index 0000000..11d7ea6 --- /dev/null +++ b/simple_api/pnpm-lock.yaml @@ -0,0 +1,455 @@ +lockfileVersion: 5.4 + +specifiers: + body-parser: ^1.19.0 + express: ^4.17.1 + nanoid: ^3.3.6 + +dependencies: + body-parser: 1.20.2 + express: 4.18.2 + nanoid: 3.3.6 + +packages: + + /accepts/1.3.8: + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: '>= 0.6'} + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + dev: false + + /array-flatten/1.1.1: + resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} + dev: false + + /body-parser/1.20.1: + resolution: {integrity: sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + on-finished: 2.4.1 + qs: 6.11.0 + raw-body: 2.5.1 + type-is: 1.6.18 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + dev: false + + /body-parser/1.20.2: + resolution: {integrity: sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + on-finished: 2.4.1 + qs: 6.11.0 + raw-body: 2.5.2 + type-is: 1.6.18 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + dev: false + + /bytes/3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + dev: false + + /call-bind/1.0.2: + resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} + dependencies: + function-bind: 1.1.1 + get-intrinsic: 1.2.1 + dev: false + + /content-disposition/0.5.4: + resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} + engines: {node: '>= 0.6'} + dependencies: + safe-buffer: 5.2.1 + dev: false + + /content-type/1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + dev: false + + /cookie-signature/1.0.6: + resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} + dev: false + + /cookie/0.5.0: + resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} + engines: {node: '>= 0.6'} + dev: false + + /debug/2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.0.0 + dev: false + + /depd/2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + dev: false + + /destroy/1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + dev: false + + /ee-first/1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + dev: false + + /encodeurl/1.0.2: + resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} + engines: {node: '>= 0.8'} + dev: false + + /escape-html/1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + dev: false + + /etag/1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + dev: false + + /express/4.18.2: + resolution: {integrity: sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==} + engines: {node: '>= 0.10.0'} + dependencies: + accepts: 1.3.8 + array-flatten: 1.1.1 + body-parser: 1.20.1 + content-disposition: 0.5.4 + content-type: 1.0.5 + cookie: 0.5.0 + cookie-signature: 1.0.6 + debug: 2.6.9 + depd: 2.0.0 + encodeurl: 1.0.2 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 1.2.0 + fresh: 0.5.2 + http-errors: 2.0.0 + merge-descriptors: 1.0.1 + methods: 1.1.2 + on-finished: 2.4.1 + parseurl: 1.3.3 + path-to-regexp: 0.1.7 + proxy-addr: 2.0.7 + qs: 6.11.0 + range-parser: 1.2.1 + safe-buffer: 5.2.1 + send: 0.18.0 + serve-static: 1.15.0 + setprototypeof: 1.2.0 + statuses: 2.0.1 + type-is: 1.6.18 + utils-merge: 1.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + dev: false + + /finalhandler/1.2.0: + resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==} + engines: {node: '>= 0.8'} + dependencies: + debug: 2.6.9 + encodeurl: 1.0.2 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.1 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + dev: false + + /forwarded/0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + dev: false + + /fresh/0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} + dev: false + + /function-bind/1.1.1: + resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} + dev: false + + /get-intrinsic/1.2.1: + resolution: {integrity: sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==} + dependencies: + function-bind: 1.1.1 + has: 1.0.3 + has-proto: 1.0.1 + has-symbols: 1.0.3 + dev: false + + /has-proto/1.0.1: + resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==} + engines: {node: '>= 0.4'} + dev: false + + /has-symbols/1.0.3: + resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} + engines: {node: '>= 0.4'} + dev: false + + /has/1.0.3: + resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} + engines: {node: '>= 0.4.0'} + dependencies: + function-bind: 1.1.1 + dev: false + + /http-errors/2.0.0: + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} + engines: {node: '>= 0.8'} + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.1 + toidentifier: 1.0.1 + dev: false + + /iconv-lite/0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + dependencies: + safer-buffer: 2.1.2 + dev: false + + /inherits/2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + dev: false + + /ipaddr.js/1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + dev: false + + /media-typer/0.3.0: + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} + engines: {node: '>= 0.6'} + dev: false + + /merge-descriptors/1.0.1: + resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==} + dev: false + + /methods/1.1.2: + resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} + engines: {node: '>= 0.6'} + dev: false + + /mime-db/1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + dev: false + + /mime-types/2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + dependencies: + mime-db: 1.52.0 + dev: false + + /mime/1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true + dev: false + + /ms/2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + dev: false + + /ms/2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + dev: false + + /nanoid/3.3.6: + resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + dev: false + + /negotiator/0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + dev: false + + /object-inspect/1.12.3: + resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==} + dev: false + + /on-finished/2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + dependencies: + ee-first: 1.1.1 + dev: false + + /parseurl/1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + dev: false + + /path-to-regexp/0.1.7: + resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} + dev: false + + /proxy-addr/2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + dev: false + + /qs/6.11.0: + resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==} + engines: {node: '>=0.6'} + dependencies: + side-channel: 1.0.4 + dev: false + + /range-parser/1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + dev: false + + /raw-body/2.5.1: + resolution: {integrity: sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==} + engines: {node: '>= 0.8'} + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + dev: false + + /raw-body/2.5.2: + resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} + engines: {node: '>= 0.8'} + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + dev: false + + /safe-buffer/5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + dev: false + + /safer-buffer/2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + dev: false + + /send/0.18.0: + resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} + engines: {node: '>= 0.8.0'} + dependencies: + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 1.0.2 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.0 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.1 + transitivePeerDependencies: + - supports-color + dev: false + + /serve-static/1.15.0: + resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==} + engines: {node: '>= 0.8.0'} + dependencies: + encodeurl: 1.0.2 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 0.18.0 + transitivePeerDependencies: + - supports-color + dev: false + + /setprototypeof/1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + dev: false + + /side-channel/1.0.4: + resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.2.1 + object-inspect: 1.12.3 + dev: false + + /statuses/2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} + dev: false + + /toidentifier/1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + dev: false + + /type-is/1.6.18: + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: '>= 0.6'} + dependencies: + media-typer: 0.3.0 + mime-types: 2.1.35 + dev: false + + /unpipe/1.0.0: + resolution: {integrity: sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=} + engines: {node: '>= 0.8'} + dev: false + + /utils-merge/1.0.1: + resolution: {integrity: sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=} + engines: {node: '>= 0.4.0'} + dev: false + + /vary/1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + dev: false diff --git a/simple_api/server.js b/simple_api/server.js new file mode 100644 index 0000000..d7688d5 --- /dev/null +++ b/simple_api/server.js @@ -0,0 +1,27 @@ +const express = require("express"); +const api = require("./api"); +const bodyParser = require("body-parser"); +const port = 3001; + +const app = express(); + +app.use(function (req, res, next) { + res.header("Access-Control-Allow-Origin", "*"); + res.header("Access-Control-Allow-Methods", "*"); + res.header( + "Access-Control-Allow-Headers", + "Origin, X-Requested-With, Content-Type, Accept" + ); + next(); +}); +app.use(bodyParser.json()); +app.use("/api", api); + +app.listen(port, "localhost", function (err) { + if (err) { + console.log(err); + return; + } + + console.log("Listening at http://localhost:" + port); +}); diff --git a/simple_api/yarn.lock b/simple_api/yarn.lock new file mode 100644 index 0000000..6e8a00f --- /dev/null +++ b/simple_api/yarn.lock @@ -0,0 +1,357 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +accepts@~1.3.7: + version "1.3.7" + resolved "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz" + integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== + dependencies: + mime-types "~2.1.24" + negotiator "0.6.2" + +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz" + integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== + +body-parser@^1.19.0, body-parser@1.19.1: + version "1.19.1" + resolved "https://registry.npmjs.org/body-parser/-/body-parser-1.19.1.tgz" + integrity sha512-8ljfQi5eBk8EJfECMrgqNGWPEY5jWP+1IzkzkGdFFEwFQZZyaZ21UqdaHktgiMlH0xLHqIFtE/u2OYE5dOtViA== + dependencies: + bytes "3.1.1" + content-type "~1.0.4" + debug "2.6.9" + depd "~1.1.2" + http-errors "1.8.1" + iconv-lite "0.4.24" + on-finished "~2.3.0" + qs "6.9.6" + raw-body "2.4.2" + type-is "~1.6.18" + +bytes@3.1.1: + version "3.1.1" + resolved "https://registry.npmjs.org/bytes/-/bytes-3.1.1.tgz" + integrity sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg== + +content-disposition@0.5.4: + version "0.5.4" + resolved "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz" + integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== + dependencies: + safe-buffer "5.2.1" + +content-type@~1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz" + integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== + +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz" + integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== + +cookie@0.4.1: + version "0.4.1" + resolved "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz" + integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA== + +debug@2.6.9: + version "2.6.9" + resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +depd@~1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz" + integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== + +destroy@~1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz" + integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= sha512-3NdhDuEXnfun/z7x9GOElY49LoqVHoGScmOKwmxhsS8N5Y+Z8KyPPDnaSzqWgYt/ji4mqwfTS34Htrk0zPIXVg== + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz" + integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz" + integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== + +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz" + integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== + +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz" + integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== + +express@^4.17.1: + version "4.17.2" + resolved "https://registry.npmjs.org/express/-/express-4.17.2.tgz" + integrity sha512-oxlxJxcQlYwqPWKVJJtvQiwHgosH/LrLSPA+H4UxpyvSS6jC5aH+5MoHFM+KABgTOt0APue4w66Ha8jCUo9QGg== + dependencies: + accepts "~1.3.7" + array-flatten "1.1.1" + body-parser "1.19.1" + content-disposition "0.5.4" + content-type "~1.0.4" + cookie "0.4.1" + cookie-signature "1.0.6" + debug "2.6.9" + depd "~1.1.2" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "~1.1.2" + fresh "0.5.2" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "~2.3.0" + parseurl "~1.3.3" + path-to-regexp "0.1.7" + proxy-addr "~2.0.7" + qs "6.9.6" + range-parser "~1.2.1" + safe-buffer "5.2.1" + send "0.17.2" + serve-static "1.14.2" + setprototypeof "1.2.0" + statuses "~1.5.0" + type-is "~1.6.18" + utils-merge "1.0.1" + vary "~1.1.2" + +finalhandler@~1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz" + integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "~2.3.0" + parseurl "~1.3.3" + statuses "~1.5.0" + unpipe "~1.0.0" + +forwarded@0.2.0: + version "0.2.0" + resolved "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz" + integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== + +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz" + integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== + +http-errors@1.8.1: + version "1.8.1" + resolved "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz" + integrity sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g== + dependencies: + depd "~1.1.2" + inherits "2.0.4" + setprototypeof "1.2.0" + statuses ">= 1.5.0 < 2" + toidentifier "1.0.1" + +iconv-lite@0.4.24: + version "0.4.24" + resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +inherits@2.0.4: + version "2.0.4" + resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +ipaddr.js@1.9.1: + version "1.9.1" + resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz" + integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== + +merge-descriptors@1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz" + integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== + +methods@~1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz" + integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== + +mime-db@1.51.0: + version "1.51.0" + resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz" + integrity sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g== + +mime-types@~2.1.24: + version "2.1.34" + resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz" + integrity sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A== + dependencies: + mime-db "1.51.0" + +mime@1.6.0: + version "1.6.0" + resolved "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== + +ms@2.1.3: + version "2.1.3" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +nanoid@^3.3.6: + version "3.3.6" + resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz" + integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA== + +negotiator@0.6.2: + version "0.6.2" + resolved "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz" + integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== + +on-finished@~2.3.0: + version "2.3.0" + resolved "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz" + integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww== + dependencies: + ee-first "1.1.1" + +parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +path-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz" + integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== + +proxy-addr@~2.0.7: + version "2.0.7" + resolved "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz" + integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== + dependencies: + forwarded "0.2.0" + ipaddr.js "1.9.1" + +qs@6.9.6: + version "6.9.6" + resolved "https://registry.npmjs.org/qs/-/qs-6.9.6.tgz" + integrity sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ== + +range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +raw-body@2.4.2: + version "2.4.2" + resolved "https://registry.npmjs.org/raw-body/-/raw-body-2.4.2.tgz" + integrity sha512-RPMAFUJP19WIet/99ngh6Iv8fzAbqum4Li7AD6DtGaW2RpMB/11xDoalPiJMTbu6I3hkbMVkATvZrqb9EEqeeQ== + dependencies: + bytes "3.1.1" + http-errors "1.8.1" + iconv-lite "0.4.24" + unpipe "1.0.0" + +safe-buffer@5.2.1: + version "5.2.1" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +send@0.17.2: + version "0.17.2" + resolved "https://registry.npmjs.org/send/-/send-0.17.2.tgz" + integrity sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww== + dependencies: + debug "2.6.9" + depd "~1.1.2" + destroy "~1.0.4" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "1.8.1" + mime "1.6.0" + ms "2.1.3" + on-finished "~2.3.0" + range-parser "~1.2.1" + statuses "~1.5.0" + +serve-static@1.14.2: + version "1.14.2" + resolved "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz" + integrity sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ== + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.17.2" + +setprototypeof@1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + +"statuses@>= 1.5.0 < 2", statuses@~1.5.0: + version "1.5.0" + resolved "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz" + integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== + +toidentifier@1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== + +type-is@~1.6.18: + version "1.6.18" + resolved "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + +unpipe@~1.0.0, unpipe@1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" + integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== + +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz" + integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== + +vary@~1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz" + integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== diff --git a/src/components/dishPage/DishPage.jsx b/src/components/dishPage/DishPage.jsx index 09f90fe..f3c03a6 100644 --- a/src/components/dishPage/DishPage.jsx +++ b/src/components/dishPage/DishPage.jsx @@ -1,17 +1,26 @@ +import { useEffect } from 'react'; import { useParams } from 'react-router-dom'; -import { useSelector } from 'react-redux'; -import { selectDishById } from '../../redux/entities/dishes/dishesSlice'; +import { useSelector, useDispatch } from 'react-redux'; +import { selectDishById, fetchDishById } from '../../redux/entities/dishes/dishesSlice'; import { DishCounter } from '../menu/DishCounter.jsx'; import { useLocation, useNavigate } from 'react-router-dom'; export const DishPage = () => { const { dishId } = useParams(); + const dispatch = useDispatch(); const dish = useSelector(state => selectDishById(state, dishId)); const location = useLocation(); const navigate = useNavigate(); const restaurantId = location.state?.restaurantId; - if (!dish) return
Блюдо не найдено
; - console.log('location.state:', location.state); + + useEffect(() => { + if (!dish) { + dispatch(fetchDishById(dishId)); + } + }, [dispatch, dishId, dish]); + + if (!dish) return
Dish not found
; + return (

{dish.name}

diff --git a/src/components/menu/MenuTab.jsx b/src/components/menu/MenuTab.jsx index e5d7492..dc1faf0 100644 --- a/src/components/menu/MenuTab.jsx +++ b/src/components/menu/MenuTab.jsx @@ -1,11 +1,29 @@ import { useParams } from 'react-router-dom'; -import { useSelector } from 'react-redux'; +import { useEffect } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; import { selectRestaurantById } from '../../redux/entities/restaurants/restaurantsSlice.js'; +import { REQUEST_STATUS } from '../../redux/request_status/requestStatus.js'; +import { fetchDishes, selectAllDishIds } from '../../redux/entities/dishes/dishesSlice.js'; import { Menu } from './Menu.jsx'; export const MenuTab = () => { + const dispatch = useDispatch(); const { restaurantId } = useParams(); const restaurant = useSelector(state => selectRestaurantById(state, restaurantId)); + const dishIds = useSelector(selectAllDishIds); + const dishesLoaded = dishIds.length > 0; + const status = useSelector(state => state.dishes.status); + const error = useSelector(state => state.dishes.error); + + useEffect(() => { + if (restaurant && !dishesLoaded) { + dispatch(fetchDishes()); + } + }, [dispatch, dishesLoaded, restaurant]); + if (!restaurant) return null; + if (status === REQUEST_STATUS.LOADING) return
Load dishes...
; + if (status === REQUEST_STATUS.FAILED) return
Error: {error}
; + return ; }; \ No newline at end of file diff --git a/src/components/restaurant/RestaurantDetailsLayout.jsx b/src/components/restaurant/RestaurantDetailsLayout.jsx index f2286ae..c2ad9b4 100644 --- a/src/components/restaurant/RestaurantDetailsLayout.jsx +++ b/src/components/restaurant/RestaurantDetailsLayout.jsx @@ -1,14 +1,20 @@ +import { useEffect } from 'react'; import { useParams, NavLink, Outlet, Navigate, useLocation } from 'react-router-dom'; -import { useSelector } from 'react-redux'; -import { selectRestaurantById } from '../../redux/entities/restaurants/restaurantsSlice'; -//import styles from './css/restaurantDetails.module.css'; +import { useSelector, useDispatch } from 'react-redux'; +import { fetchRestaurantById, selectRestaurantById } from '../../redux/entities/restaurants/restaurantsSlice'; import styles from '../restaurant/css/restaurantTab.module.css'; export const RestaurantDetailsLayout = () => { const { restaurantId } = useParams(); + const dispatch = useDispatch(); const location = useLocation(); const restaurant = useSelector(state => selectRestaurantById(state, restaurantId)); - if (!restaurant) return
Ресторан не найден
; + useEffect(() => { + if (!restaurant) { + dispatch(fetchRestaurantById(restaurantId)); + } + }, [dispatch, restaurantId, restaurant]); + if (!restaurant) return
Load...
; if (location.pathname === `/restaurants/${restaurantId}`) { return ; diff --git a/src/components/restaurant/RestaurantTabs.jsx b/src/components/restaurant/RestaurantTabs.jsx index f77b1d9..2846b13 100644 --- a/src/components/restaurant/RestaurantTabs.jsx +++ b/src/components/restaurant/RestaurantTabs.jsx @@ -1,14 +1,22 @@ -import { useSelector } from 'react-redux'; +import { useEffect } from 'react'; +import { useSelector, useDispatch } from 'react-redux'; import { RestaurantDetails } from './RestaurantDetails.jsx'; import { useTheme } from '../context/themeContext/ThemeContext.jsx'; import { RestaurantTabButton } from './RestaurantTabButton.jsx'; -import { selectAllRestaurantIds } from '../../redux/entities/restaurants/restaurantsSlice'; +import { selectAllRestaurantIds, fetchRestaurants } from '../../redux/entities/restaurants/restaurantsSlice'; +import { REQUEST_STATUS } from '../../redux/request_status/requestStatus.js'; import styles from './css/restaurantTab.module.css'; import { Outlet } from 'react-router'; import { useNavigate, useParams } from 'react-router-dom'; export const RestaurantTabs = () => { + const dispatch = useDispatch(); const restaurantIds = useSelector(selectAllRestaurantIds); + useEffect(() => { + if (!restaurantIds.length) { + dispatch(fetchRestaurants()); + } + }, [dispatch, restaurantIds.length]); const { restaurantId } = useParams(); const navigate = useNavigate(); const { theme } = useTheme(); diff --git a/src/components/reviews/ReviewsTab.jsx b/src/components/reviews/ReviewsTab.jsx index 06c8eaf..fbd7e24 100644 --- a/src/components/reviews/ReviewsTab.jsx +++ b/src/components/reviews/ReviewsTab.jsx @@ -1,11 +1,46 @@ +import { useEffect } from 'react'; import { useParams } from 'react-router-dom'; -import { useSelector } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import { selectRestaurantById } from '../../redux/entities/restaurants/restaurantsSlice.js'; +import { fetchReviews } from '../../redux/entities/reviews/reviewsSlice.js'; +import { fetchUsers } from '../../redux/entities/users/usersSlice.js'; import { Reviews } from './Reviews.jsx'; +import { REQUEST_STATUS } from '../../redux/request_status/requestStatus.js'; export const ReviewsTab = () => { const { restaurantId } = useParams(); + const dispatch = useDispatch(); const restaurant = useSelector(state => selectRestaurantById(state, restaurantId)); + const usersLoaded = useSelector(state => state.users.ids.length > 0); + + const reviewsStatus = useSelector(state => state.reviews.status); + const reviewsError = useSelector(state => state.reviews.error); + const usersStatus = useSelector(state => state.users.status); + const usersError = useSelector(state => state.users.error); + + useEffect(() => { + if (restaurant) { + dispatch(fetchReviews(restaurantId)); + } + }, [dispatch, restaurantId, restaurant]); + + useEffect(() => { + if (!usersLoaded) { + dispatch(fetchUsers()); + } + }, [dispatch, usersLoaded]); + if (!restaurant) return null; + + if (reviewsStatus === REQUEST_STATUS.LOADING || usersStatus === REQUEST_STATUS.LOADING) { + return
Load reviews...
; + } + if (reviewsStatus === REQUEST_STATUS.FAILED) { + return
Error uploading reviews: {reviewsError}
; + } + if (usersStatus === REQUEST_STATUS.FAILED) { + return
User upload error: {usersError}
; + } + return ; }; \ No newline at end of file diff --git a/src/redux/entities/dishes/dishesSlice.js b/src/redux/entities/dishes/dishesSlice.js index 6a0f91b..c02ec60 100644 --- a/src/redux/entities/dishes/dishesSlice.js +++ b/src/redux/entities/dishes/dishesSlice.js @@ -1,30 +1,59 @@ -import { createSlice } from "@reduxjs/toolkit"; -import { createSelector } from "reselect"; -import { normalizedDishes } from '../../../../materials/normalized-mock.js'; +import { createSlice, createAsyncThunk, createEntityAdapter } from "@reduxjs/toolkit"; +import { REQUEST_STATUS } from '../../request_status/requestStatus'; -const initialState = { - ids: normalizedDishes.map(({ id }) => id), - entities: normalizedDishes.reduce((acc, dish) => { - acc[dish.id] = dish; - return acc; - }, {}), -}; +const dishesAdapter = createEntityAdapter(); + +export const fetchDishes = createAsyncThunk( + 'dishes/fetchDishes', + async () => { + const response = await fetch('http://localhost:3001/api/dishes/'); + return await response.json(); + } +); + +export const fetchDishById = createAsyncThunk( + 'dishes/fetchDishById', + async (dishId) => { + const response = await fetch(`http://localhost:3001/api/dish/${dishId}`); + if (!response.ok) throw new Error('Error fetching dish by ID'); + return await response.json(); + } +); + +const initialState = dishesAdapter.getInitialState({ + status: REQUEST_STATUS.IDLE, + error: null, +}); export const dishesSlice = createSlice({ name: "dishes", initialState, - selectors: { - selectDishesIds: (state) => state.ids, - selectDishById: (state, dishId) => state.entities[dishId], - }, + reducers: {}, + extraReducers: (builder) => { + builder + .addCase(fetchDishes.pending, (state) => { + state.status = REQUEST_STATUS.LOADING; + state.error = null; + }) + .addCase(fetchDishes.fulfilled, (state, action) => { + state.status = REQUEST_STATUS.SUCCEEDED; + state.error = null; + dishesAdapter.setAll(state, action.payload); + }) + .addCase(fetchDishes.rejected, (state, action) => { + state.status = REQUEST_STATUS.FAILED; + state.error = action.error.message; + }) + .addCase(fetchDishById.fulfilled, (state, action) => { + dishesAdapter.upsertOne(state, action.payload); + }); + } }); -export const { selectDishesIds, selectDishById } = dishesSlice.selectors; +export const { + selectAll: selectAllDishes, + selectById: selectDishById, + selectIds: selectAllDishIds, +} = dishesAdapter.getSelectors(state => state.dishes); -export const selectDishesByIds = createSelector( - [ - (state, dishIds) => dishIds, - (state) => state.dishes.entities - ], - (dishIds, entities) => dishIds.map(id => entities[id]) -); \ No newline at end of file +export default dishesSlice.reducer; \ No newline at end of file diff --git a/src/redux/entities/restaurants/restaurantsSlice.js b/src/redux/entities/restaurants/restaurantsSlice.js index b43b05c..0ced282 100644 --- a/src/redux/entities/restaurants/restaurantsSlice.js +++ b/src/redux/entities/restaurants/restaurantsSlice.js @@ -1,21 +1,58 @@ -import { createSlice } from "@reduxjs/toolkit"; -import { normalizedRestaurants } from '../../../../materials/normalized-mock.js'; +import { createSlice, createAsyncThunk, createEntityAdapter } from "@reduxjs/toolkit"; -const initialState = { - ids: normalizedRestaurants.map(({ id }) => id), - entities: normalizedRestaurants.reduce((acc, restaurant) => { - acc[restaurant.id] = restaurant; - return acc; - }, {}), -}; +const restaurantsAdapter = createEntityAdapter(); + +export const fetchRestaurants = createAsyncThunk( + 'restaurants/fetchRestaurants', + async () => { + const response = await fetch('http://localhost:3001/api/restaurants/'); + return await response.json(); + } +); + +export const fetchRestaurantById = createAsyncThunk( + 'restaurants/fetchRestaurantById', + async (restaurantId) => { + const response = await fetch(`http://localhost:3001/api/restaurant/${restaurantId}`); + if (!response.ok) throw new Error('Error fetching restaurant by ID'); + return await response.json(); + } +); + +const initialState = restaurantsAdapter.getInitialState({ + loading: false, + error: null, +}); export const restaurantsSlice = createSlice({ name: "restaurants", initialState, - selectors: { - selectAllRestaurantIds: (state) => state.ids, - selectRestaurantById: (state, restaurantId) => state.entities[restaurantId], - }, + reducers: {}, + extraReducers: (builder) => { + builder + .addCase(fetchRestaurants.pending, (state) => { + state.loading = true; + state.error = null; + }) + .addCase(fetchRestaurants.fulfilled, (state, action) => { + state.loading = false; + state.error = null; + restaurantsAdapter.setAll(state, action.payload); + }) + .addCase(fetchRestaurants.rejected, (state, action) => { + state.loading = false; + state.error = action.error.message; + }) + .addCase(fetchRestaurantById.fulfilled, (state, action) => { + restaurantsAdapter.upsertOne(state, action.payload); + }); + } }); -export const { selectAllRestaurantIds, selectRestaurantById } = restaurantsSlice.selectors; \ No newline at end of file +export const { + selectAll: selectAllRestaurants, + selectById: selectRestaurantById, + selectIds: selectAllRestaurantIds, +} = restaurantsAdapter.getSelectors(state => state.restaurants); + +export default restaurantsSlice.reducer; \ No newline at end of file diff --git a/src/redux/entities/reviews/reviewsSlice.js b/src/redux/entities/reviews/reviewsSlice.js index 9ba569b..3fa258d 100644 --- a/src/redux/entities/reviews/reviewsSlice.js +++ b/src/redux/entities/reviews/reviewsSlice.js @@ -1,30 +1,47 @@ -import { createSlice } from "@reduxjs/toolkit"; -import { createSelector } from "reselect"; -import { normalizedReviews } from '../../../../materials/normalized-mock.js'; +import { createSlice, createAsyncThunk, createEntityAdapter } from "@reduxjs/toolkit"; -const initialState = { - ids: normalizedReviews.map(({ id }) => id), - entities: normalizedReviews.reduce((acc, review) => { - acc[review.id] = review; - return acc; - }, {}), -}; +const reviewsAdapter = createEntityAdapter(); + +export const fetchReviews = createAsyncThunk( + 'reviews/fetchReviews', + async (restaurantId) => { + const response = await fetch(`http://localhost:3001/api/reviews?restaurantId=${restaurantId}`); + if (!response.ok) throw new Error('Error fetching reviews'); + return await response.json(); + } +); + +const initialState = reviewsAdapter.getInitialState({ + loading: false, + error: null, +}); export const reviewsSlice = createSlice({ name: "reviews", initialState, - selectors: { - selectAllReviews: (state) => state.ids, - selectReviewById: (state, reviewId) => state.entities[reviewId], - }, + reducers: {}, + extraReducers: (builder) => { + builder + .addCase(fetchReviews.pending, (state) => { + state.loading = true; + state.error = null; + }) + .addCase(fetchReviews.fulfilled, (state, action) => { + state.loading = false; + state.error = null; + reviewsAdapter.setAll(state, action.payload); + }) + .addCase(fetchReviews.rejected, (state, action) => { + state.loading = false; + state.error = action.error.message; + }); + } }); -export const { selectAllReviews, selectReviewById } = reviewsSlice.selectors; +export const { + selectAll: selectAllReviews, + selectById: selectReviewById, + selectIds: selectAllReviewIds, +} = reviewsAdapter.getSelectors(state => state.reviews); -export const selectReviewsByIds = createSelector( - [ - (state, reviewIds) => reviewIds, - (state) => state.reviews.entities - ], - (reviewIds, entities) => reviewIds.map(id => entities[id]) -); \ No newline at end of file +export default reviewsSlice.reducer; \ No newline at end of file diff --git a/src/redux/entities/users/usersSlice.js b/src/redux/entities/users/usersSlice.js index 0a1530c..b85c7d8 100644 --- a/src/redux/entities/users/usersSlice.js +++ b/src/redux/entities/users/usersSlice.js @@ -1,17 +1,47 @@ -import { createSlice } from "@reduxjs/toolkit"; -import { normalizedUsers } from '../../../../materials/normalized-mock.js'; +import { createSlice, createAsyncThunk, createEntityAdapter } from "@reduxjs/toolkit"; -const initialState = { - ids: normalizedUsers.map(({ id }) => id), - entities: normalizedUsers.reduce((acc, user) => { - acc[user.id] = user; - return acc; - }, {}), -}; +const usersAdapter = createEntityAdapter(); + +export const fetchUsers = createAsyncThunk( + 'users/fetchUsers', + async () => { + const response = await fetch('http://localhost:3001/api/users/'); + if (!response.ok) throw new Error('Error fetching users'); + return await response.json(); + } +); + +const initialState = usersAdapter.getInitialState({ + loading: false, + error: null, +}); export const usersSlice = createSlice({ name: "users", initialState, + reducers: {}, + extraReducers: (builder) => { + builder + .addCase(fetchUsers.pending, (state) => { + state.loading = true; + state.error = null; + }) + .addCase(fetchUsers.fulfilled, (state, action) => { + state.loading = false; + state.error = null; + usersAdapter.setAll(state, action.payload); + }) + .addCase(fetchUsers.rejected, (state, action) => { + state.loading = false; + state.error = action.error.message; + }); + } }); -export const selectUserById = (state, userId) => state.users.entities[userId]; \ No newline at end of file +export const { + selectAll: selectAllUsers, + selectById: selectUserById, + selectIds: selectAllUserIds, +} = usersAdapter.getSelectors(state => state.users); + +export default usersSlice.reducer; \ No newline at end of file diff --git a/src/redux/request_status/requestStatus.js b/src/redux/request_status/requestStatus.js new file mode 100644 index 0000000..25305d9 --- /dev/null +++ b/src/redux/request_status/requestStatus.js @@ -0,0 +1,6 @@ +export const REQUEST_STATUS = { + IDLE: 'idle', + LOADING: 'loading', + SUCCEEDED: 'succeeded', + FAILED: 'failed', +}; \ No newline at end of file From d976cc41764262ff8ae2387d8eb0de7299b1c533 Mon Sep 17 00:00:00 2001 From: AntonV1211 Date: Sun, 22 Jun 2025 15:22:54 +0700 Subject: [PATCH 2/6] Fix review --- simple_api/README.md | 1 + src/components/dishPage/DishPage.jsx | 13 +++-- src/components/menu/MenuTab.jsx | 11 ++-- .../restaurant/RestaurantDetailsLayout.jsx | 23 ++++---- src/components/restaurant/RestaurantTabs.jsx | 12 ++--- src/components/reviews/ReviewsTab.jsx | 18 +++---- src/redux/entities/dishes/dishesSlice.js | 25 +++------ src/redux/entities/dishes/dishesThunks.js | 53 +++++++++++++++++++ .../entities/restaurants/restaurantsSlice.js | 20 +------ .../entities/restaurants/restaurantsThunks.js | 24 +++++++++ src/redux/entities/reviews/reviewsSlice.js | 15 ++---- src/redux/entities/reviews/reviewsThunks.js | 18 +++++++ src/redux/entities/users/usersSlice.js | 12 +---- src/redux/entities/users/usersThunks.js | 16 ++++++ 14 files changed, 162 insertions(+), 99 deletions(-) create mode 100644 src/redux/entities/dishes/dishesThunks.js create mode 100644 src/redux/entities/restaurants/restaurantsThunks.js create mode 100644 src/redux/entities/reviews/reviewsThunks.js create mode 100644 src/redux/entities/users/usersThunks.js diff --git a/simple_api/README.md b/simple_api/README.md index 574219f..0a132d8 100644 --- a/simple_api/README.md +++ b/simple_api/README.md @@ -1,4 +1,5 @@ # API +npm start-server ## Ресторан diff --git a/src/components/dishPage/DishPage.jsx b/src/components/dishPage/DishPage.jsx index 0b4f983..e893fa1 100644 --- a/src/components/dishPage/DishPage.jsx +++ b/src/components/dishPage/DishPage.jsx @@ -1,8 +1,9 @@ import { useEffect } from 'react'; -import { useSelector, useDispatch, useParams } from 'react-redux'; -import { selectDishById, fetchDishById } from '../../redux/entities/dishes/dishesSlice'; +import { useSelector, useDispatch } from 'react-redux'; +import { useParams, useLocation, useNavigate } from 'react-router'; +import { selectDishById } from '../../redux/entities/dishes/dishesSlice'; +import { fetchDishById } from '../../redux/entities/dishes/dishesThunks.js'; import { DishCounter } from '../menu/DishCounter.jsx'; -import { useLocation, useNavigate, useParams } from 'react-router'; export const DishPage = () => { const { dishId } = useParams(); @@ -13,10 +14,8 @@ export const DishPage = () => { const restaurantId = location.state?.restaurantId; useEffect(() => { - if (!dish) { - dispatch(fetchDishById(dishId)); - } - }, [dispatch, dishId, dish]); + dispatch(fetchDishById(dishId)); + }, [dispatch, dishId]); if (!dish) return
Dish not found
; diff --git a/src/components/menu/MenuTab.jsx b/src/components/menu/MenuTab.jsx index 8671343..fbea2e1 100644 --- a/src/components/menu/MenuTab.jsx +++ b/src/components/menu/MenuTab.jsx @@ -1,9 +1,8 @@ -import { useParams } from 'react-router'; import { useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { selectRestaurantById } from '../../redux/entities/restaurants/restaurantsSlice.js'; +import { fetchDishesByRestaurantId } from '../../redux/entities/dishes/dishesThunks.js'; import { REQUEST_STATUS } from '../../redux/request_status/requestStatus.js'; -import { fetchDishes, selectAllDishIds } from '../../redux/entities/dishes/dishesSlice.js'; import { Menu } from './Menu.jsx'; import { useParams } from 'react-router'; @@ -12,16 +11,14 @@ export const MenuTab = () => { const dispatch = useDispatch(); const { restaurantId } = useParams(); const restaurant = useSelector(state => selectRestaurantById(state, restaurantId)); - const dishIds = useSelector(selectAllDishIds); - const dishesLoaded = dishIds.length > 0; const status = useSelector(state => state.dishes.status); const error = useSelector(state => state.dishes.error); useEffect(() => { - if (restaurant && !dishesLoaded) { - dispatch(fetchDishes()); + if (restaurant) { + dispatch(fetchDishesByRestaurantId(restaurant.id)); } - }, [dispatch, dishesLoaded, restaurant]); + }, [dispatch, restaurant]); if (!restaurant) return null; if (status === REQUEST_STATUS.LOADING) return
Load dishes...
; diff --git a/src/components/restaurant/RestaurantDetailsLayout.jsx b/src/components/restaurant/RestaurantDetailsLayout.jsx index b682eda..5de3a21 100644 --- a/src/components/restaurant/RestaurantDetailsLayout.jsx +++ b/src/components/restaurant/RestaurantDetailsLayout.jsx @@ -1,7 +1,8 @@ import { useEffect } from 'react'; -import { useParams, NavLink, Outlet, useLocation, useNavigate, Navigate } from 'react-router'; +import { useParams, NavLink, Outlet, useLocation, useNavigate } from 'react-router'; import { useSelector, useDispatch } from 'react-redux'; -import { fetchRestaurantById, selectRestaurantById } from '../../redux/entities/restaurants/restaurantsSlice'; +import { selectRestaurantById } from '../../redux/entities/restaurants/restaurantsSlice'; +import { fetchRestaurantById } from '../../redux/entities/restaurants/restaurantsThunks'; import styles from '../restaurant/css/restaurantTab.module.css'; import classNames from 'classnames'; @@ -9,19 +10,21 @@ export const RestaurantDetailsLayout = () => { const { restaurantId } = useParams(); const dispatch = useDispatch(); const location = useLocation(); + const navigate = useNavigate(); const restaurant = useSelector(state => selectRestaurantById(state, restaurantId)); + + useEffect(() => { + dispatch(fetchRestaurantById(restaurantId)); + }, [dispatch, restaurantId]); + useEffect(() => { - if (!restaurant) { - dispatch(fetchRestaurantById(restaurantId)); + if (restaurant && location.pathname === `/restaurants/${restaurantId}`) { + navigate('menu', { replace: true }); } - }, [dispatch, restaurantId, restaurant]); + }, [restaurant, location.pathname, restaurantId, navigate]); if (!restaurant) return
Load...
; - if (location.pathname === `/restaurants/${restaurantId}`) { - return ; - } - return (

{restaurant.name}

@@ -33,7 +36,7 @@ export const RestaurantDetailsLayout = () => { classNames(styles.tab, { [styles.activeTab]: isActive }) } > - Menuы + Menu { const dispatch = useDispatch(); const restaurantIds = useSelector(selectAllRestaurantIds); + useEffect(() => { - if (!restaurantIds.length) { - dispatch(fetchRestaurants()); - } - }, [dispatch, restaurantIds.length]); + dispatch(fetchRestaurants()); + }, [dispatch]); + const { restaurantId } = useParams(); const { theme } = useTheme(); diff --git a/src/components/reviews/ReviewsTab.jsx b/src/components/reviews/ReviewsTab.jsx index faa67cb..f1f1c6c 100644 --- a/src/components/reviews/ReviewsTab.jsx +++ b/src/components/reviews/ReviewsTab.jsx @@ -2,8 +2,8 @@ import { useEffect } from 'react'; import { useParams } from 'react-router'; import { useDispatch, useSelector } from 'react-redux'; import { selectRestaurantById } from '../../redux/entities/restaurants/restaurantsSlice.js'; -import { fetchReviews } from '../../redux/entities/reviews/reviewsSlice.js'; -import { fetchUsers } from '../../redux/entities/users/usersSlice.js'; +import { fetchReviews } from '../../redux/entities/reviews/reviewsThunks.js'; +import { fetchUsers } from '../../redux/entities/users/usersThunks.js'; import { Reviews } from './Reviews.jsx'; import { REQUEST_STATUS } from '../../redux/request_status/requestStatus.js'; @@ -11,24 +11,18 @@ export const ReviewsTab = () => { const { restaurantId } = useParams(); const dispatch = useDispatch(); const restaurant = useSelector(state => selectRestaurantById(state, restaurantId)); - const usersLoaded = useSelector(state => state.users.ids.length > 0); - const reviewsStatus = useSelector(state => state.reviews.status); const reviewsError = useSelector(state => state.reviews.error); const usersStatus = useSelector(state => state.users.status); const usersError = useSelector(state => state.users.error); useEffect(() => { - if (restaurant) { - dispatch(fetchReviews(restaurantId)); - } - }, [dispatch, restaurantId, restaurant]); + dispatch(fetchReviews(restaurantId)); + }, [dispatch, restaurantId]); useEffect(() => { - if (!usersLoaded) { - dispatch(fetchUsers()); - } - }, [dispatch, usersLoaded]); + dispatch(fetchUsers()); + }, [dispatch]); if (!restaurant) return null; diff --git a/src/redux/entities/dishes/dishesSlice.js b/src/redux/entities/dishes/dishesSlice.js index c02ec60..eaac63f 100644 --- a/src/redux/entities/dishes/dishesSlice.js +++ b/src/redux/entities/dishes/dishesSlice.js @@ -1,25 +1,9 @@ -import { createSlice, createAsyncThunk, createEntityAdapter } from "@reduxjs/toolkit"; +import { createSlice, createEntityAdapter } from "@reduxjs/toolkit"; import { REQUEST_STATUS } from '../../request_status/requestStatus'; +import { fetchDishes, fetchDishById, fetchDishesByRestaurantId } from './dishesThunks'; const dishesAdapter = createEntityAdapter(); -export const fetchDishes = createAsyncThunk( - 'dishes/fetchDishes', - async () => { - const response = await fetch('http://localhost:3001/api/dishes/'); - return await response.json(); - } -); - -export const fetchDishById = createAsyncThunk( - 'dishes/fetchDishById', - async (dishId) => { - const response = await fetch(`http://localhost:3001/api/dish/${dishId}`); - if (!response.ok) throw new Error('Error fetching dish by ID'); - return await response.json(); - } -); - const initialState = dishesAdapter.getInitialState({ status: REQUEST_STATUS.IDLE, error: null, @@ -38,7 +22,7 @@ export const dishesSlice = createSlice({ .addCase(fetchDishes.fulfilled, (state, action) => { state.status = REQUEST_STATUS.SUCCEEDED; state.error = null; - dishesAdapter.setAll(state, action.payload); + dishesAdapter.setMany(state, action.payload); }) .addCase(fetchDishes.rejected, (state, action) => { state.status = REQUEST_STATUS.FAILED; @@ -46,6 +30,9 @@ export const dishesSlice = createSlice({ }) .addCase(fetchDishById.fulfilled, (state, action) => { dishesAdapter.upsertOne(state, action.payload); + }) + .addCase(fetchDishesByRestaurantId.fulfilled, (state, action) => { + dishesAdapter.upsertMany(state, action.payload); }); } }); diff --git a/src/redux/entities/dishes/dishesThunks.js b/src/redux/entities/dishes/dishesThunks.js new file mode 100644 index 0000000..4035e31 --- /dev/null +++ b/src/redux/entities/dishes/dishesThunks.js @@ -0,0 +1,53 @@ +import { createAsyncThunk } from "@reduxjs/toolkit"; +import { selectDishById } from "./dishesSlice"; + +export const fetchDishes = createAsyncThunk( + 'dishes/fetchDishes', + async () => { + const response = await fetch('http://localhost:3001/api/dishes/'); + return await response.json(); + }, + { + condition: (_, { getState }) => { + const state = getState(); + const hasRestaurant = state.restaurants.ids.length > 0; + const dishesNotLoaded = state.dishes.ids.length === 0; + return hasRestaurant && dishesNotLoaded; + } + } +); + +export const fetchDishById = createAsyncThunk( + 'dishes/fetchDishById', + async (dishId) => { + const response = await fetch(`http://localhost:3001/api/dish/${dishId}`); + if (!response.ok) throw new Error('Error fetching dish by ID'); + return await response.json(); + }, + { + condition: (dishId, { getState }) => { + const state = getState(); + const dish = selectDishById(state, dishId); + return !dish; + } + } +); + +export const fetchDishesByRestaurantId = createAsyncThunk( + 'dishes/fetchDishesByRestaurantId', + async (restaurantId) => { + const response = await fetch(`http://localhost:3001/api/dishes?restaurantId=${restaurantId}`); + if (!response.ok) throw new Error('Error fetching dishes'); + return await response.json(); + }, + { + condition: (restaurantId, { getState }) => { + const state = getState(); + const dishIds = state.dishes.ids; + const dishes = dishIds + .map(id => state.dishes.entities[id]) + .filter(dish => dish && dish.restaurantId === restaurantId); + return dishes.length === 0; + } + } +); \ No newline at end of file diff --git a/src/redux/entities/restaurants/restaurantsSlice.js b/src/redux/entities/restaurants/restaurantsSlice.js index 0ced282..97ac025 100644 --- a/src/redux/entities/restaurants/restaurantsSlice.js +++ b/src/redux/entities/restaurants/restaurantsSlice.js @@ -1,24 +1,8 @@ -import { createSlice, createAsyncThunk, createEntityAdapter } from "@reduxjs/toolkit"; +import { createSlice, createEntityAdapter } from "@reduxjs/toolkit"; +import { fetchRestaurants, fetchRestaurantById } from './restaurantsThunks'; const restaurantsAdapter = createEntityAdapter(); -export const fetchRestaurants = createAsyncThunk( - 'restaurants/fetchRestaurants', - async () => { - const response = await fetch('http://localhost:3001/api/restaurants/'); - return await response.json(); - } -); - -export const fetchRestaurantById = createAsyncThunk( - 'restaurants/fetchRestaurantById', - async (restaurantId) => { - const response = await fetch(`http://localhost:3001/api/restaurant/${restaurantId}`); - if (!response.ok) throw new Error('Error fetching restaurant by ID'); - return await response.json(); - } -); - const initialState = restaurantsAdapter.getInitialState({ loading: false, error: null, diff --git a/src/redux/entities/restaurants/restaurantsThunks.js b/src/redux/entities/restaurants/restaurantsThunks.js new file mode 100644 index 0000000..98288dd --- /dev/null +++ b/src/redux/entities/restaurants/restaurantsThunks.js @@ -0,0 +1,24 @@ +import { createAsyncThunk } from "@reduxjs/toolkit"; + +export const fetchRestaurants = createAsyncThunk( + 'restaurants/fetchRestaurants', + async () => { + const response = await fetch('http://localhost:3001/api/restaurants/'); + return await response.json(); + } +); + +export const fetchRestaurantById = createAsyncThunk( + 'restaurants/fetchRestaurantById', + async (restaurantId) => { + const response = await fetch(`http://localhost:3001/api/restaurant/${restaurantId}`); + if (!response.ok) throw new Error('Error fetching restaurant by ID'); + return await response.json(); + }, + { + condition: (restaurantId, { getState }) => { + const state = getState(); + return !state.restaurants.entities[restaurantId]; + } + } +); \ No newline at end of file diff --git a/src/redux/entities/reviews/reviewsSlice.js b/src/redux/entities/reviews/reviewsSlice.js index 3fa258d..64be687 100644 --- a/src/redux/entities/reviews/reviewsSlice.js +++ b/src/redux/entities/reviews/reviewsSlice.js @@ -1,16 +1,8 @@ -import { createSlice, createAsyncThunk, createEntityAdapter } from "@reduxjs/toolkit"; +import { createSlice, createEntityAdapter } from "@reduxjs/toolkit"; +import { fetchReviews } from './reviewsThunks'; const reviewsAdapter = createEntityAdapter(); -export const fetchReviews = createAsyncThunk( - 'reviews/fetchReviews', - async (restaurantId) => { - const response = await fetch(`http://localhost:3001/api/reviews?restaurantId=${restaurantId}`); - if (!response.ok) throw new Error('Error fetching reviews'); - return await response.json(); - } -); - const initialState = reviewsAdapter.getInitialState({ loading: false, error: null, @@ -44,4 +36,7 @@ export const { selectIds: selectAllReviewIds, } = reviewsAdapter.getSelectors(state => state.reviews); +export const selectReviewsByRestaurantId = (state, restaurantId) => + selectAllReviews(state).filter(review => review.restaurantId === restaurantId); + export default reviewsSlice.reducer; \ No newline at end of file diff --git a/src/redux/entities/reviews/reviewsThunks.js b/src/redux/entities/reviews/reviewsThunks.js new file mode 100644 index 0000000..55f2e95 --- /dev/null +++ b/src/redux/entities/reviews/reviewsThunks.js @@ -0,0 +1,18 @@ +import { createAsyncThunk } from "@reduxjs/toolkit"; +import { selectReviewsByRestaurantId } from "./reviewsSlice"; + +export const fetchReviews = createAsyncThunk( + 'reviews/fetchReviews', + async (restaurantId) => { + const response = await fetch(`http://localhost:3001/api/reviews?restaurantId=${restaurantId}`); + if (!response.ok) throw new Error('Error fetching reviews'); + return await response.json(); + }, + { + condition: (restaurantId, { getState }) => { + const state = getState(); + const reviews = selectReviewsByRestaurantId(state, restaurantId); + return reviews.length === 0; + } + } +); \ No newline at end of file diff --git a/src/redux/entities/users/usersSlice.js b/src/redux/entities/users/usersSlice.js index b85c7d8..3a1a70d 100644 --- a/src/redux/entities/users/usersSlice.js +++ b/src/redux/entities/users/usersSlice.js @@ -1,16 +1,8 @@ -import { createSlice, createAsyncThunk, createEntityAdapter } from "@reduxjs/toolkit"; +import { createSlice, createEntityAdapter } from "@reduxjs/toolkit"; +import { fetchUsers } from './usersThunks'; const usersAdapter = createEntityAdapter(); -export const fetchUsers = createAsyncThunk( - 'users/fetchUsers', - async () => { - const response = await fetch('http://localhost:3001/api/users/'); - if (!response.ok) throw new Error('Error fetching users'); - return await response.json(); - } -); - const initialState = usersAdapter.getInitialState({ loading: false, error: null, diff --git a/src/redux/entities/users/usersThunks.js b/src/redux/entities/users/usersThunks.js new file mode 100644 index 0000000..853ab21 --- /dev/null +++ b/src/redux/entities/users/usersThunks.js @@ -0,0 +1,16 @@ +import { createAsyncThunk } from "@reduxjs/toolkit"; + +export const fetchUsers = createAsyncThunk( + 'users/fetchUsers', + async () => { + const response = await fetch('http://localhost:3001/api/users/'); + if (!response.ok) throw new Error('Error fetching users'); + return await response.json(); + }, + { + condition: (_, { getState }) => { + const state = getState(); + return state.users.ids.length === 0; + } + } +); \ No newline at end of file From c9b1ea58ca5c7336551901c77168ea65379fa9c3 Mon Sep 17 00:00:00 2001 From: AntonV1211 Date: Sun, 22 Jun 2025 16:06:20 +0700 Subject: [PATCH 3/6] Fix review --- src/redux/entities/restaurants/restaurantsThunks.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/redux/entities/restaurants/restaurantsThunks.js b/src/redux/entities/restaurants/restaurantsThunks.js index 98288dd..c3e88e9 100644 --- a/src/redux/entities/restaurants/restaurantsThunks.js +++ b/src/redux/entities/restaurants/restaurantsThunks.js @@ -1,4 +1,5 @@ import { createAsyncThunk } from "@reduxjs/toolkit"; +import { selectRestaurantById } from './restaurantsSlice'; export const fetchRestaurants = createAsyncThunk( 'restaurants/fetchRestaurants', @@ -18,7 +19,7 @@ export const fetchRestaurantById = createAsyncThunk( { condition: (restaurantId, { getState }) => { const state = getState(); - return !state.restaurants.entities[restaurantId]; + return !selectRestaurantById(state, restaurantId); } } ); \ No newline at end of file From 17ed1f1d4300bad2eb01565f9b82a111bc2410a7 Mon Sep 17 00:00:00 2001 From: AntonV1211 Date: Sun, 22 Jun 2025 19:32:38 +0700 Subject: [PATCH 4/6] hw-10 --- src/components/cart/CartItem.jsx | 15 ++++++ src/components/cart/cart.jsx | 17 +++--- src/components/dishPage/DishPage.jsx | 14 ++--- src/components/layout/Layout.jsx | 2 +- src/components/menu/MenuItem.jsx | 6 +-- src/components/menu/MenuTab.jsx | 29 ++++------ .../restaurant/RestaurantDetails.jsx | 6 +-- .../restaurant/RestaurantDetailsLayout.jsx | 19 +++---- .../restaurant/RestaurantTabButton.jsx | 5 +- src/components/restaurant/RestaurantTabs.jsx | 27 ++++------ src/components/reviewForm/ReviewForm.jsx | 27 ++++++++-- src/components/reviews/Review.jsx | 10 ++-- src/components/reviews/Reviews.jsx | 19 +++++-- src/components/reviews/ReviewsTab.jsx | 38 +++++-------- src/hooks/useCounter.js | 2 +- src/redux/api/dishesApi.js | 19 +++++++ src/redux/api/restaurantsApi.js | 19 +++++++ src/redux/api/reviewsApi.js | 34 ++++++++++++ src/redux/api/usersApi.js | 19 +++++++ src/redux/{ => entities}/cart/cartSlise.js | 0 src/redux/entities/dishes/dishesSlice.js | 46 ---------------- src/redux/entities/dishes/dishesThunks.js | 53 ------------------- .../entities/restaurants/restaurantsSlice.js | 42 --------------- .../entities/restaurants/restaurantsThunks.js | 25 --------- src/redux/entities/reviews/reviewsSlice.js | 42 --------------- src/redux/entities/reviews/reviewsThunks.js | 18 ------- src/redux/entities/users/usersSlice.js | 39 -------------- src/redux/entities/users/usersThunks.js | 16 ------ src/redux/store.js | 25 +++++---- 29 files changed, 220 insertions(+), 413 deletions(-) create mode 100644 src/components/cart/CartItem.jsx create mode 100644 src/redux/api/dishesApi.js create mode 100644 src/redux/api/restaurantsApi.js create mode 100644 src/redux/api/reviewsApi.js create mode 100644 src/redux/api/usersApi.js rename src/redux/{ => entities}/cart/cartSlise.js (100%) delete mode 100644 src/redux/entities/dishes/dishesSlice.js delete mode 100644 src/redux/entities/dishes/dishesThunks.js delete mode 100644 src/redux/entities/restaurants/restaurantsSlice.js delete mode 100644 src/redux/entities/restaurants/restaurantsThunks.js delete mode 100644 src/redux/entities/reviews/reviewsSlice.js delete mode 100644 src/redux/entities/reviews/reviewsThunks.js delete mode 100644 src/redux/entities/users/usersSlice.js delete mode 100644 src/redux/entities/users/usersThunks.js diff --git a/src/components/cart/CartItem.jsx b/src/components/cart/CartItem.jsx new file mode 100644 index 0000000..5ae4cb1 --- /dev/null +++ b/src/components/cart/CartItem.jsx @@ -0,0 +1,15 @@ +import { useGetDishByIdQuery } from '../../redux/api/dishesApi'; + +export const CartItem = ({ id, amount }) => { + const { data: dish, isLoading } = useGetDishByIdQuery(id); + + return ( +
  • + {isLoading + ? 'Loading...' + : dish + ? `${dish.name} - ${amount}` + : 'Unknown'} +
  • + ); +}; \ No newline at end of file diff --git a/src/components/cart/cart.jsx b/src/components/cart/cart.jsx index 07f2fe8..e10c792 100644 --- a/src/components/cart/cart.jsx +++ b/src/components/cart/cart.jsx @@ -1,9 +1,9 @@ import { useSelector } from 'react-redux'; -import { selectCartItems } from '../../redux/cart/cartSlise'; +import { selectCartItems } from '../../redux/entities/cart/cartSlise.js'; +import { CartItem } from './CartItem.jsx'; export const Cart = () => { const items = useSelector(selectCartItems); - const dishes = useSelector(state => state.dishes.entities); // Получаем все блюда if (!items.length) { return null; @@ -15,15 +15,10 @@ export const Cart = () => {

    Cart:

      - {items.map(({ id, amount }) => { - const dish = dishes[id]; - return ( -
    • - {dish ? dish.name : 'Unknown'} - {amount} -
    • - ); - })} + {items.map(({ id, amount }) => ( + + ))}
    ); -} \ No newline at end of file +}; \ No newline at end of file diff --git a/src/components/dishPage/DishPage.jsx b/src/components/dishPage/DishPage.jsx index e893fa1..364b389 100644 --- a/src/components/dishPage/DishPage.jsx +++ b/src/components/dishPage/DishPage.jsx @@ -1,22 +1,16 @@ -import { useEffect } from 'react'; -import { useSelector, useDispatch } from 'react-redux'; import { useParams, useLocation, useNavigate } from 'react-router'; -import { selectDishById } from '../../redux/entities/dishes/dishesSlice'; -import { fetchDishById } from '../../redux/entities/dishes/dishesThunks.js'; +import { useGetDishByIdQuery } from '../../redux/api/dishesApi'; import { DishCounter } from '../menu/DishCounter.jsx'; export const DishPage = () => { const { dishId } = useParams(); - const dispatch = useDispatch(); - const dish = useSelector(state => selectDishById(state, dishId)); + const { data: dish, isLoading, error } = useGetDishByIdQuery(dishId); const location = useLocation(); const navigate = useNavigate(); const restaurantId = location.state?.restaurantId; - useEffect(() => { - dispatch(fetchDishById(dishId)); - }, [dispatch, dishId]); - + if (isLoading) return
    Loading...
    ; + if (error) return
    Error loading dish
    ; if (!dish) return
    Dish not found
    ; return ( diff --git a/src/components/layout/Layout.jsx b/src/components/layout/Layout.jsx index 38385ab..1ae90f9 100644 --- a/src/components/layout/Layout.jsx +++ b/src/components/layout/Layout.jsx @@ -2,7 +2,7 @@ import { Header } from './Header.jsx'; import { Footer } from './Footer.jsx'; import { ProgressBar } from '../progressBar/ProgressBar.jsx'; import styles from './css/layout.module.css'; -import { Cart } from '../cart/cart.jsx'; +import { Cart } from '../cart/Cart.jsx'; import { Outlet } from 'react-router'; export const Layout = () => { diff --git a/src/components/menu/MenuItem.jsx b/src/components/menu/MenuItem.jsx index 4ca6079..4f2f4bb 100644 --- a/src/components/menu/MenuItem.jsx +++ b/src/components/menu/MenuItem.jsx @@ -1,14 +1,14 @@ -import { useSelector } from 'react-redux'; -import { selectDishById } from '../../redux/entities/dishes/dishesSlice'; +import { useGetDishByIdQuery } from '../../redux/api/dishesApi'; import style from './css/menuItem.module.css'; import { DishCounter } from './DishCounter.jsx'; import { useUser } from '../../hooks/useUser'; import { Link } from 'react-router'; export const MenuItem = ({ dishId, restaurantId }) => { - const dish = useSelector(state => selectDishById(state, dishId)); + const { data: dish, isLoading } = useGetDishByIdQuery(dishId); const { user } = useUser(); + if (isLoading) return
  • Loading...
  • ; if (!dish) return null; return ( diff --git a/src/components/menu/MenuTab.jsx b/src/components/menu/MenuTab.jsx index fbea2e1..27a9ad2 100644 --- a/src/components/menu/MenuTab.jsx +++ b/src/components/menu/MenuTab.jsx @@ -1,28 +1,17 @@ -import { useEffect } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import { selectRestaurantById } from '../../redux/entities/restaurants/restaurantsSlice.js'; -import { fetchDishesByRestaurantId } from '../../redux/entities/dishes/dishesThunks.js'; -import { REQUEST_STATUS } from '../../redux/request_status/requestStatus.js'; -import { Menu } from './Menu.jsx'; +import { useGetDishesByRestaurantIdQuery } from '../../redux/api/dishesApi'; import { useParams } from 'react-router'; - +import { useGetRestaurantByIdQuery } from '../../redux/api/restaurantsApi'; +import { Menu } from './Menu.jsx'; export const MenuTab = () => { - const dispatch = useDispatch(); const { restaurantId } = useParams(); - const restaurant = useSelector(state => selectRestaurantById(state, restaurantId)); - const status = useSelector(state => state.dishes.status); - const error = useSelector(state => state.dishes.error); - - useEffect(() => { - if (restaurant) { - dispatch(fetchDishesByRestaurantId(restaurant.id)); - } - }, [dispatch, restaurant]); + const { data: restaurant, isLoading: restaurantLoading } = useGetRestaurantByIdQuery(restaurantId); + const { data: dishes, isLoading: dishesLoading, error } = useGetDishesByRestaurantIdQuery(restaurantId); if (!restaurant) return null; - if (status === REQUEST_STATUS.LOADING) return
    Load dishes...
    ; - if (status === REQUEST_STATUS.FAILED) return
    Error: {error}
    ; + if (restaurantLoading || dishesLoading) return
    Load dishes...
    ; + if (error) return
    Error: {error.message}
    ; + if (!dishes) return null; - return ; + return d.id)} restaurantId={restaurant.id} />; }; \ No newline at end of file diff --git a/src/components/restaurant/RestaurantDetails.jsx b/src/components/restaurant/RestaurantDetails.jsx index fb470cc..601233b 100644 --- a/src/components/restaurant/RestaurantDetails.jsx +++ b/src/components/restaurant/RestaurantDetails.jsx @@ -1,11 +1,11 @@ -import { useSelector } from 'react-redux'; -import { selectRestaurantById } from '../../redux/entities/restaurants/restaurantsSlice'; +import { useGetRestaurantByIdQuery } from '../../redux/api/restaurantsApi'; import { Menu } from '../menu/Menu.jsx'; import { Reviews } from '../reviews/Reviews.jsx'; import style from './css/restaurantDetails.module.css'; export const RestaurantDetails = ({ restaurantId }) => { - const restaurant = useSelector(state => selectRestaurantById(state, restaurantId)); + const { data: restaurant, isLoading } = useGetRestaurantByIdQuery(restaurantId); + if (isLoading) return
    Loading...
    ; if (!restaurant) return null; return ( diff --git a/src/components/restaurant/RestaurantDetailsLayout.jsx b/src/components/restaurant/RestaurantDetailsLayout.jsx index 5de3a21..bd63154 100644 --- a/src/components/restaurant/RestaurantDetailsLayout.jsx +++ b/src/components/restaurant/RestaurantDetailsLayout.jsx @@ -1,29 +1,24 @@ -import { useEffect } from 'react'; +import React from 'react'; import { useParams, NavLink, Outlet, useLocation, useNavigate } from 'react-router'; -import { useSelector, useDispatch } from 'react-redux'; -import { selectRestaurantById } from '../../redux/entities/restaurants/restaurantsSlice'; -import { fetchRestaurantById } from '../../redux/entities/restaurants/restaurantsThunks'; +import { useGetRestaurantByIdQuery } from '../../redux/api/restaurantsApi'; import styles from '../restaurant/css/restaurantTab.module.css'; import classNames from 'classnames'; export const RestaurantDetailsLayout = () => { const { restaurantId } = useParams(); - const dispatch = useDispatch(); + const { data: restaurant, isLoading } = useGetRestaurantByIdQuery(restaurantId); const location = useLocation(); const navigate = useNavigate(); - const restaurant = useSelector(state => selectRestaurantById(state, restaurantId)); - useEffect(() => { - dispatch(fetchRestaurantById(restaurantId)); - }, [dispatch, restaurantId]); - - useEffect(() => { + // Автоматический переход на menu + React.useEffect(() => { if (restaurant && location.pathname === `/restaurants/${restaurantId}`) { navigate('menu', { replace: true }); } }, [restaurant, location.pathname, restaurantId, navigate]); - if (!restaurant) return
    Load...
    ; + if (isLoading) return
    Load...
    ; + if (!restaurant) return
    Not found
    ; return (
    diff --git a/src/components/restaurant/RestaurantTabButton.jsx b/src/components/restaurant/RestaurantTabButton.jsx index 3923b48..ad7160a 100644 --- a/src/components/restaurant/RestaurantTabButton.jsx +++ b/src/components/restaurant/RestaurantTabButton.jsx @@ -1,10 +1,7 @@ -import { useSelector } from 'react-redux'; -import { selectRestaurantById } from '../../redux/entities/restaurants/restaurantsSlice'; import classNames from 'classnames'; import styles from './css/restaurantTab.module.css'; -export const RestaurantTabButton = ({ restaurantId, active, theme, onClick }) => { - const restaurant = useSelector(state => selectRestaurantById(state, restaurantId)); +export const RestaurantTabButton = ({ restaurant, active, theme, onClick }) => { if (!restaurant) return null; return ( diff --git a/src/components/reviews/Review.jsx b/src/components/reviews/Review.jsx index f082ae8..48b5d24 100644 --- a/src/components/reviews/Review.jsx +++ b/src/components/reviews/Review.jsx @@ -1,10 +1,8 @@ -import { useSelector } from 'react-redux'; -import { selectReviewById } from '../../redux/entities/reviews/reviewsSlice'; -import { selectUserById } from '../../redux/entities/users/usersSlice'; +import { useGetUsersQuery } from '../../redux/api/usersApi'; -export const Review = ({ reviewId }) => { - const review = useSelector(state => selectReviewById(state, reviewId)); - const user = useSelector(state => selectUserById(state, review?.userId)); +export const Review = ({ review, userId }) => { + const { data: users } = useGetUsersQuery(); + const user = users?.find(u => u.id === userId); if (!review) return null; diff --git a/src/components/reviews/Reviews.jsx b/src/components/reviews/Reviews.jsx index dd8042b..6279dcd 100644 --- a/src/components/reviews/Reviews.jsx +++ b/src/components/reviews/Reviews.jsx @@ -1,19 +1,28 @@ import { Review } from './Review.jsx'; +import { useGetReviewsByRestaurantIdQuery } from '../../redux/api/reviewsApi'; import { ReviewForm } from '../reviewForm/ReviewForm.jsx'; import { useUser } from '../../hooks/useUser'; -export const Reviews = ({ reviewIds }) => { +export const Reviews = ({ restaurantId }) => { + const { data: reviews, isLoading } = useGetReviewsByRestaurantIdQuery(restaurantId); const { user } = useUser(); + if (isLoading) return
    Loading...
    ; + if (!reviews?.length) return ( + <> +
    No reviews
    + {user && } + + ); + return ( <> -

    Reviews:

      - {reviewIds.map(id => ( - + {reviews.map(review => ( + ))}
    - {user && } + {user && } ); }; \ No newline at end of file diff --git a/src/components/reviews/ReviewsTab.jsx b/src/components/reviews/ReviewsTab.jsx index f1f1c6c..e494b22 100644 --- a/src/components/reviews/ReviewsTab.jsx +++ b/src/components/reviews/ReviewsTab.jsx @@ -1,40 +1,26 @@ -import { useEffect } from 'react'; import { useParams } from 'react-router'; -import { useDispatch, useSelector } from 'react-redux'; -import { selectRestaurantById } from '../../redux/entities/restaurants/restaurantsSlice.js'; -import { fetchReviews } from '../../redux/entities/reviews/reviewsThunks.js'; -import { fetchUsers } from '../../redux/entities/users/usersThunks.js'; import { Reviews } from './Reviews.jsx'; -import { REQUEST_STATUS } from '../../redux/request_status/requestStatus.js'; +import { useGetReviewsByRestaurantIdQuery } from '../../redux/api/reviewsApi'; +import { useGetUsersQuery } from '../../redux/api/usersApi'; +import { useGetRestaurantByIdQuery } from '../../redux/api/restaurantsApi'; export const ReviewsTab = () => { const { restaurantId } = useParams(); - const dispatch = useDispatch(); - const restaurant = useSelector(state => selectRestaurantById(state, restaurantId)); - const reviewsStatus = useSelector(state => state.reviews.status); - const reviewsError = useSelector(state => state.reviews.error); - const usersStatus = useSelector(state => state.users.status); - const usersError = useSelector(state => state.users.error); - - useEffect(() => { - dispatch(fetchReviews(restaurantId)); - }, [dispatch, restaurantId]); - - useEffect(() => { - dispatch(fetchUsers()); - }, [dispatch]); + const { data: restaurant, isLoading: restaurantLoading } = useGetRestaurantByIdQuery(restaurantId); + const { data: reviews, isLoading: reviewsLoading, error: reviewsError } = useGetReviewsByRestaurantIdQuery(restaurantId); + const { isLoading: usersLoading, error: usersError } = useGetUsersQuery(); if (!restaurant) return null; - if (reviewsStatus === REQUEST_STATUS.LOADING || usersStatus === REQUEST_STATUS.LOADING) { + if (restaurantLoading || reviewsLoading || usersLoading) { return
    Load reviews...
    ; } - if (reviewsStatus === REQUEST_STATUS.FAILED) { - return
    Error uploading reviews: {reviewsError}
    ; + if (reviewsError) { + return
    Error uploading reviews: {reviewsError.message}
    ; } - if (usersStatus === REQUEST_STATUS.FAILED) { - return
    User upload error: {usersError}
    ; + if (usersError) { + return
    User upload error: {usersError.message}
    ; } - return ; + return r.id) ?? []} restaurantId={restaurantId} />; }; \ No newline at end of file diff --git a/src/hooks/useCounter.js b/src/hooks/useCounter.js index 09c3d04..775390d 100644 --- a/src/hooks/useCounter.js +++ b/src/hooks/useCounter.js @@ -1,6 +1,6 @@ import { useCallback } from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import { addToCart, removeFromCart, selectItemAmountById } from '../redux/cart/cartSlise'; +import { addToCart, removeFromCart, selectItemAmountById } from '../redux/entities/cart/cartSlise'; export const useCounter = (dishId) => { const dispatch = useDispatch(); diff --git a/src/redux/api/dishesApi.js b/src/redux/api/dishesApi.js new file mode 100644 index 0000000..630b10f --- /dev/null +++ b/src/redux/api/dishesApi.js @@ -0,0 +1,19 @@ +import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; + +export const dishesApi = createApi({ + reducerPath: 'dishesApi', + baseQuery: fetchBaseQuery({ baseUrl: 'http://localhost:3001/api/' }), + endpoints: (builder) => ({ + getDishesByRestaurantId: builder.query({ + query: (restaurantId) => `dishes?restaurantId=${restaurantId}`, + }), + getDishById: builder.query({ + query: (dishId) => `dish/${dishId}`, + }), + }), +}); + +export const { + useGetDishesByRestaurantIdQuery, + useGetDishByIdQuery, +} = dishesApi; \ No newline at end of file diff --git a/src/redux/api/restaurantsApi.js b/src/redux/api/restaurantsApi.js new file mode 100644 index 0000000..52b6455 --- /dev/null +++ b/src/redux/api/restaurantsApi.js @@ -0,0 +1,19 @@ +import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; + +export const restaurantsApi = createApi({ + reducerPath: 'restaurantsApi', + baseQuery: fetchBaseQuery({ baseUrl: 'http://localhost:3001/api/' }), + endpoints: (builder) => ({ + getRestaurants: builder.query({ + query: () => 'restaurants/', + }), + getRestaurantById: builder.query({ + query: (restaurantId) => `restaurant/${restaurantId}`, + }), + }), +}); + +export const { + useGetRestaurantsQuery, + useGetRestaurantByIdQuery, +} = restaurantsApi; \ No newline at end of file diff --git a/src/redux/api/reviewsApi.js b/src/redux/api/reviewsApi.js new file mode 100644 index 0000000..246d7ca --- /dev/null +++ b/src/redux/api/reviewsApi.js @@ -0,0 +1,34 @@ +import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; + +export const reviewsApi = createApi({ + reducerPath: 'reviewsApi', + baseQuery: fetchBaseQuery({ baseUrl: 'http://localhost:3001/api/' }), + tagTypes: ['Reviews'], + endpoints: (builder) => ({ + getReviewsByRestaurantId: builder.query({ + query: (restaurantId) => `reviews?restaurantId=${restaurantId}`, + providesTags: (result, error, restaurantId) => [ + { type: 'Reviews', id: restaurantId } + ], + }), + getReviewById: builder.query({ + query: (reviewId) => `reviews/${reviewId}`, + }), + addReview: builder.mutation({ + query: ({ restaurantId, ...review }) => ({ + url: `review/${restaurantId}`, + method: 'POST', + body: review, + }), + invalidatesTags: (result, error, { restaurantId }) => [ + { type: 'Reviews', id: restaurantId } + ], + }), + }), +}); + +export const { + useGetReviewsByRestaurantIdQuery, + useGetReviewByIdQuery, + useAddReviewMutation, +} = reviewsApi; \ No newline at end of file diff --git a/src/redux/api/usersApi.js b/src/redux/api/usersApi.js new file mode 100644 index 0000000..0ecf717 --- /dev/null +++ b/src/redux/api/usersApi.js @@ -0,0 +1,19 @@ +import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; + +export const usersApi = createApi({ + reducerPath: 'usersApi', + baseQuery: fetchBaseQuery({ baseUrl: 'http://localhost:3001/api/' }), + endpoints: (builder) => ({ + getUsers: builder.query({ + query: () => 'users/', + }), + getUserById: builder.query({ + query: (userId) => `users/${userId}`, + }), + }), +}); + +export const { + useGetUsersQuery, + useGetUserByIdQuery, +} = usersApi; \ No newline at end of file diff --git a/src/redux/cart/cartSlise.js b/src/redux/entities/cart/cartSlise.js similarity index 100% rename from src/redux/cart/cartSlise.js rename to src/redux/entities/cart/cartSlise.js diff --git a/src/redux/entities/dishes/dishesSlice.js b/src/redux/entities/dishes/dishesSlice.js deleted file mode 100644 index eaac63f..0000000 --- a/src/redux/entities/dishes/dishesSlice.js +++ /dev/null @@ -1,46 +0,0 @@ -import { createSlice, createEntityAdapter } from "@reduxjs/toolkit"; -import { REQUEST_STATUS } from '../../request_status/requestStatus'; -import { fetchDishes, fetchDishById, fetchDishesByRestaurantId } from './dishesThunks'; - -const dishesAdapter = createEntityAdapter(); - -const initialState = dishesAdapter.getInitialState({ - status: REQUEST_STATUS.IDLE, - error: null, -}); - -export const dishesSlice = createSlice({ - name: "dishes", - initialState, - reducers: {}, - extraReducers: (builder) => { - builder - .addCase(fetchDishes.pending, (state) => { - state.status = REQUEST_STATUS.LOADING; - state.error = null; - }) - .addCase(fetchDishes.fulfilled, (state, action) => { - state.status = REQUEST_STATUS.SUCCEEDED; - state.error = null; - dishesAdapter.setMany(state, action.payload); - }) - .addCase(fetchDishes.rejected, (state, action) => { - state.status = REQUEST_STATUS.FAILED; - state.error = action.error.message; - }) - .addCase(fetchDishById.fulfilled, (state, action) => { - dishesAdapter.upsertOne(state, action.payload); - }) - .addCase(fetchDishesByRestaurantId.fulfilled, (state, action) => { - dishesAdapter.upsertMany(state, action.payload); - }); - } -}); - -export const { - selectAll: selectAllDishes, - selectById: selectDishById, - selectIds: selectAllDishIds, -} = dishesAdapter.getSelectors(state => state.dishes); - -export default dishesSlice.reducer; \ No newline at end of file diff --git a/src/redux/entities/dishes/dishesThunks.js b/src/redux/entities/dishes/dishesThunks.js deleted file mode 100644 index 4035e31..0000000 --- a/src/redux/entities/dishes/dishesThunks.js +++ /dev/null @@ -1,53 +0,0 @@ -import { createAsyncThunk } from "@reduxjs/toolkit"; -import { selectDishById } from "./dishesSlice"; - -export const fetchDishes = createAsyncThunk( - 'dishes/fetchDishes', - async () => { - const response = await fetch('http://localhost:3001/api/dishes/'); - return await response.json(); - }, - { - condition: (_, { getState }) => { - const state = getState(); - const hasRestaurant = state.restaurants.ids.length > 0; - const dishesNotLoaded = state.dishes.ids.length === 0; - return hasRestaurant && dishesNotLoaded; - } - } -); - -export const fetchDishById = createAsyncThunk( - 'dishes/fetchDishById', - async (dishId) => { - const response = await fetch(`http://localhost:3001/api/dish/${dishId}`); - if (!response.ok) throw new Error('Error fetching dish by ID'); - return await response.json(); - }, - { - condition: (dishId, { getState }) => { - const state = getState(); - const dish = selectDishById(state, dishId); - return !dish; - } - } -); - -export const fetchDishesByRestaurantId = createAsyncThunk( - 'dishes/fetchDishesByRestaurantId', - async (restaurantId) => { - const response = await fetch(`http://localhost:3001/api/dishes?restaurantId=${restaurantId}`); - if (!response.ok) throw new Error('Error fetching dishes'); - return await response.json(); - }, - { - condition: (restaurantId, { getState }) => { - const state = getState(); - const dishIds = state.dishes.ids; - const dishes = dishIds - .map(id => state.dishes.entities[id]) - .filter(dish => dish && dish.restaurantId === restaurantId); - return dishes.length === 0; - } - } -); \ No newline at end of file diff --git a/src/redux/entities/restaurants/restaurantsSlice.js b/src/redux/entities/restaurants/restaurantsSlice.js deleted file mode 100644 index 97ac025..0000000 --- a/src/redux/entities/restaurants/restaurantsSlice.js +++ /dev/null @@ -1,42 +0,0 @@ -import { createSlice, createEntityAdapter } from "@reduxjs/toolkit"; -import { fetchRestaurants, fetchRestaurantById } from './restaurantsThunks'; - -const restaurantsAdapter = createEntityAdapter(); - -const initialState = restaurantsAdapter.getInitialState({ - loading: false, - error: null, -}); - -export const restaurantsSlice = createSlice({ - name: "restaurants", - initialState, - reducers: {}, - extraReducers: (builder) => { - builder - .addCase(fetchRestaurants.pending, (state) => { - state.loading = true; - state.error = null; - }) - .addCase(fetchRestaurants.fulfilled, (state, action) => { - state.loading = false; - state.error = null; - restaurantsAdapter.setAll(state, action.payload); - }) - .addCase(fetchRestaurants.rejected, (state, action) => { - state.loading = false; - state.error = action.error.message; - }) - .addCase(fetchRestaurantById.fulfilled, (state, action) => { - restaurantsAdapter.upsertOne(state, action.payload); - }); - } -}); - -export const { - selectAll: selectAllRestaurants, - selectById: selectRestaurantById, - selectIds: selectAllRestaurantIds, -} = restaurantsAdapter.getSelectors(state => state.restaurants); - -export default restaurantsSlice.reducer; \ No newline at end of file diff --git a/src/redux/entities/restaurants/restaurantsThunks.js b/src/redux/entities/restaurants/restaurantsThunks.js deleted file mode 100644 index c3e88e9..0000000 --- a/src/redux/entities/restaurants/restaurantsThunks.js +++ /dev/null @@ -1,25 +0,0 @@ -import { createAsyncThunk } from "@reduxjs/toolkit"; -import { selectRestaurantById } from './restaurantsSlice'; - -export const fetchRestaurants = createAsyncThunk( - 'restaurants/fetchRestaurants', - async () => { - const response = await fetch('http://localhost:3001/api/restaurants/'); - return await response.json(); - } -); - -export const fetchRestaurantById = createAsyncThunk( - 'restaurants/fetchRestaurantById', - async (restaurantId) => { - const response = await fetch(`http://localhost:3001/api/restaurant/${restaurantId}`); - if (!response.ok) throw new Error('Error fetching restaurant by ID'); - return await response.json(); - }, - { - condition: (restaurantId, { getState }) => { - const state = getState(); - return !selectRestaurantById(state, restaurantId); - } - } -); \ No newline at end of file diff --git a/src/redux/entities/reviews/reviewsSlice.js b/src/redux/entities/reviews/reviewsSlice.js deleted file mode 100644 index 64be687..0000000 --- a/src/redux/entities/reviews/reviewsSlice.js +++ /dev/null @@ -1,42 +0,0 @@ -import { createSlice, createEntityAdapter } from "@reduxjs/toolkit"; -import { fetchReviews } from './reviewsThunks'; - -const reviewsAdapter = createEntityAdapter(); - -const initialState = reviewsAdapter.getInitialState({ - loading: false, - error: null, -}); - -export const reviewsSlice = createSlice({ - name: "reviews", - initialState, - reducers: {}, - extraReducers: (builder) => { - builder - .addCase(fetchReviews.pending, (state) => { - state.loading = true; - state.error = null; - }) - .addCase(fetchReviews.fulfilled, (state, action) => { - state.loading = false; - state.error = null; - reviewsAdapter.setAll(state, action.payload); - }) - .addCase(fetchReviews.rejected, (state, action) => { - state.loading = false; - state.error = action.error.message; - }); - } -}); - -export const { - selectAll: selectAllReviews, - selectById: selectReviewById, - selectIds: selectAllReviewIds, -} = reviewsAdapter.getSelectors(state => state.reviews); - -export const selectReviewsByRestaurantId = (state, restaurantId) => - selectAllReviews(state).filter(review => review.restaurantId === restaurantId); - -export default reviewsSlice.reducer; \ No newline at end of file diff --git a/src/redux/entities/reviews/reviewsThunks.js b/src/redux/entities/reviews/reviewsThunks.js deleted file mode 100644 index 55f2e95..0000000 --- a/src/redux/entities/reviews/reviewsThunks.js +++ /dev/null @@ -1,18 +0,0 @@ -import { createAsyncThunk } from "@reduxjs/toolkit"; -import { selectReviewsByRestaurantId } from "./reviewsSlice"; - -export const fetchReviews = createAsyncThunk( - 'reviews/fetchReviews', - async (restaurantId) => { - const response = await fetch(`http://localhost:3001/api/reviews?restaurantId=${restaurantId}`); - if (!response.ok) throw new Error('Error fetching reviews'); - return await response.json(); - }, - { - condition: (restaurantId, { getState }) => { - const state = getState(); - const reviews = selectReviewsByRestaurantId(state, restaurantId); - return reviews.length === 0; - } - } -); \ No newline at end of file diff --git a/src/redux/entities/users/usersSlice.js b/src/redux/entities/users/usersSlice.js deleted file mode 100644 index 3a1a70d..0000000 --- a/src/redux/entities/users/usersSlice.js +++ /dev/null @@ -1,39 +0,0 @@ -import { createSlice, createEntityAdapter } from "@reduxjs/toolkit"; -import { fetchUsers } from './usersThunks'; - -const usersAdapter = createEntityAdapter(); - -const initialState = usersAdapter.getInitialState({ - loading: false, - error: null, -}); - -export const usersSlice = createSlice({ - name: "users", - initialState, - reducers: {}, - extraReducers: (builder) => { - builder - .addCase(fetchUsers.pending, (state) => { - state.loading = true; - state.error = null; - }) - .addCase(fetchUsers.fulfilled, (state, action) => { - state.loading = false; - state.error = null; - usersAdapter.setAll(state, action.payload); - }) - .addCase(fetchUsers.rejected, (state, action) => { - state.loading = false; - state.error = action.error.message; - }); - } -}); - -export const { - selectAll: selectAllUsers, - selectById: selectUserById, - selectIds: selectAllUserIds, -} = usersAdapter.getSelectors(state => state.users); - -export default usersSlice.reducer; \ No newline at end of file diff --git a/src/redux/entities/users/usersThunks.js b/src/redux/entities/users/usersThunks.js deleted file mode 100644 index 853ab21..0000000 --- a/src/redux/entities/users/usersThunks.js +++ /dev/null @@ -1,16 +0,0 @@ -import { createAsyncThunk } from "@reduxjs/toolkit"; - -export const fetchUsers = createAsyncThunk( - 'users/fetchUsers', - async () => { - const response = await fetch('http://localhost:3001/api/users/'); - if (!response.ok) throw new Error('Error fetching users'); - return await response.json(); - }, - { - condition: (_, { getState }) => { - const state = getState(); - return state.users.ids.length === 0; - } - } -); \ No newline at end of file diff --git a/src/redux/store.js b/src/redux/store.js index ba3046f..663749d 100644 --- a/src/redux/store.js +++ b/src/redux/store.js @@ -1,16 +1,23 @@ import { configureStore } from "@reduxjs/toolkit"; -import { restaurantsSlice } from "./entities/restaurants/restaurantsSlice"; -import { dishesSlice } from "./entities/dishes/dishesSlice"; -import { reviewsSlice } from "./entities/reviews/reviewsSlice"; -import { usersSlice } from "./entities/users/usersSlice"; -import { cartSlice } from "./cart/cartSlise"; +import { cartSlice } from "./entities/cart/cartSlise"; +import { dishesApi } from "./api/dishesApi"; +import { usersApi } from "./api/usersApi"; +import { reviewsApi } from "./api/reviewsApi"; +import { restaurantsApi } from "./api/restaurantsApi"; export const store = configureStore({ reducer: { - [restaurantsSlice.name]: restaurantsSlice.reducer, - [dishesSlice.name]: dishesSlice.reducer, - [reviewsSlice.name]: reviewsSlice.reducer, - [usersSlice.name]: usersSlice.reducer, [cartSlice.name]: cartSlice.reducer, + [restaurantsApi.reducerPath]: restaurantsApi.reducer, + [dishesApi.reducerPath]: dishesApi.reducer, + [usersApi.reducerPath]: usersApi.reducer, + [reviewsApi.reducerPath]: reviewsApi.reducer, }, + middleware: (getDefaultMiddleware) => + getDefaultMiddleware().concat( + dishesApi.middleware, + usersApi.middleware, + reviewsApi.middleware, + restaurantsApi.middleware + ), }); \ No newline at end of file From 9cf30a77fd8fb864459fc1922f91b133f4ea8ebd Mon Sep 17 00:00:00 2001 From: AntonV1211 Date: Sun, 22 Jun 2025 20:30:42 +0700 Subject: [PATCH 5/6] Editing reviews --- src/components/reviewForm/ReviewForm.jsx | 2 +- src/components/reviews/Review.jsx | 42 +++++++++++++++++++++++- src/redux/api/reviewsApi.js | 11 +++++++ 3 files changed, 53 insertions(+), 2 deletions(-) diff --git a/src/components/reviewForm/ReviewForm.jsx b/src/components/reviewForm/ReviewForm.jsx index e19b7d6..723c1a0 100644 --- a/src/components/reviewForm/ReviewForm.jsx +++ b/src/components/reviewForm/ReviewForm.jsx @@ -17,7 +17,7 @@ export const ReviewForm = ({ restaurantId }) => { if (!user) return; await addReview({ restaurantId, - userId: "a304959a-76c0-4b34-954a-b38dbf310360",//user.id, + userId: "a304959a-76c0-4b34-954a-b38dbf310360",//"a304959a-76c0-4b34-954a-b38dbf310360",//user.id, text: state.text, rating: state.rating, }); diff --git a/src/components/reviews/Review.jsx b/src/components/reviews/Review.jsx index 48b5d24..48891d9 100644 --- a/src/components/reviews/Review.jsx +++ b/src/components/reviews/Review.jsx @@ -1,16 +1,56 @@ import { useGetUsersQuery } from '../../redux/api/usersApi'; +import { useState } from 'react'; +import { useUpdateReviewMutation } from '../../redux/api/reviewsApi'; export const Review = ({ review, userId }) => { const { data: users } = useGetUsersQuery(); const user = users?.find(u => u.id === userId); + // Проверяем, что отзыв принадлежит текущему пользователю + const isOwnReview = userId === "a304959a-76c0-4b34-954a-b38dbf310360"; + + const [isEditing, setIsEditing] = useState(false); + const [text, setText] = useState(review.text); + const [rating, setRating] = useState(review.rating); + const [updateReview, { isLoading }] = useUpdateReviewMutation(); + + const handleSave = async () => { + await updateReview({ + reviewId: review.id, + restaurantId: review.restaurantId, + text, + rating, + }); + setIsEditing(false); + }; + if (!review) return null; return (
  • {user ? user.name : 'Unknown user'} - — {review.text} (Rating: {review.rating}) + {isEditing && isOwnReview ? ( + <> +