From ee110a9ad822c5d1fcae4a4018333d846bde24e0 Mon Sep 17 00:00:00 2001
From: "a.shamavov" <a.shamavov@iqdev.digital>
Date: Fri, 26 Apr 2024 11:57:14 +0500
Subject: [PATCH] =?UTF-8?q?STA-966=20|=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2?=
 =?UTF-8?q?=D0=B8=D0=BB=20=D0=BA=D0=BE=D0=BD=D1=82=D1=80=D0=BE=D0=BB=D0=BB?=
 =?UTF-8?q?=D0=B5=D1=80=D1=8B=20=D0=B4=D0=BB=D1=8F=20News=20=D0=B8=20?=
 =?UTF-8?q?=D0=B4=D1=80=D1=83=D0=B3=D0=B8=D0=B5=20=D0=B8=D1=81=D0=BF=D1=80?=
 =?UTF-8?q?=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .gitignore                                    |  4 +-
 docker-compose.yaml                           |  4 +-
 ...24072545.php => Version20240426064235.php} |  2 +-
 src/Controller/NewsController.php             | 55 +++++++++++
 src/Controller/RestaurantController.php       | 30 ++++--
 src/Entity/News.php                           |  2 +-
 src/Entity/NewsCategory.php                   |  3 +
 src/Entity/Restaurant.php                     |  6 +-
 src/Exception/NewsExceptionEnum.php           |  8 ++
 src/Exception/RestaurantExceptionEnum.php     |  8 ++
 src/Mapper/NewsMapper.php                     | 93 +++++++++++++++++++
 src/Mapper/RestaurantMapper.php               | 79 ++++++++++++++--
 src/Model/NewsCategory.php                    |  8 +-
 src/Model/NewsDetailElement.php               |  8 +-
 src/Model/NewsListingElement.php              |  8 +-
 src/Model/RestaurantDetailElement.php         | 14 +--
 .../NewsCategoryRepositoryInterface.php       |  8 ++
 .../Interface/NewsRepositoryInterface.php     | 13 +++
 .../RestaurantRepositoryInterface.php         |  4 +-
 src/Repository/NewsCategoryRepository.php     | 31 ++-----
 src/Repository/NewsRepository.php             | 59 +++++++-----
 src/Repository/RestaurantRepository.php       |  6 +-
 src/Requests/NewsListRequest.php              | 28 ++++++
 src/Requests/RestaurantListRequest.php        | 28 ++++++
 src/Service/NewsService.php                   | 48 ++++++++++
 src/Service/RestaurantService.php             | 48 +++-------
 26 files changed, 471 insertions(+), 134 deletions(-)
 rename migrations/{Version20240424072545.php => Version20240426064235.php} (99%)
 create mode 100644 src/Controller/NewsController.php
 create mode 100644 src/Exception/NewsExceptionEnum.php
 create mode 100644 src/Exception/RestaurantExceptionEnum.php
 create mode 100644 src/Mapper/NewsMapper.php
 create mode 100644 src/Repository/Interface/NewsCategoryRepositoryInterface.php
 create mode 100644 src/Repository/Interface/NewsRepositoryInterface.php
 create mode 100644 src/Requests/NewsListRequest.php
 create mode 100644 src/Requests/RestaurantListRequest.php
 create mode 100644 src/Service/NewsService.php

diff --git a/.gitignore b/.gitignore
index 930e1e2..f8810d3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -23,4 +23,6 @@
 /public/assets/
 /assets/vendor/
 ###< symfony/asset-mapper ###
-/.idea
\ No newline at end of file
+/.idea
+.env
+.env.test
\ No newline at end of file
diff --git a/docker-compose.yaml b/docker-compose.yaml
index b87becc..5548380 100644
--- a/docker-compose.yaml
+++ b/docker-compose.yaml
@@ -9,7 +9,7 @@ services:
       POSTGRES_PASSWORD: 12345
       POSTGRES_HOST_AUTH_METHOD: trust
     ports:
-      - "5433:${DATABASE_PORT}"
+      - "${DATABASE_PORT}:${DATABASE_PORT}"
     networks:
       - internal
   nginx:
@@ -33,8 +33,6 @@ services:
         APP_BASE_DIR: ${APP_BASE_DIR-.}
     volumes:
       - ".:/app"
-    ports:
-      - "9000:9000"
     restart: unless-stopped
     networks:
       - internal
diff --git a/migrations/Version20240424072545.php b/migrations/Version20240426064235.php
similarity index 99%
rename from migrations/Version20240424072545.php
rename to migrations/Version20240426064235.php
index b77fac7..9142542 100644
--- a/migrations/Version20240424072545.php
+++ b/migrations/Version20240426064235.php
@@ -10,7 +10,7 @@ use Doctrine\Migrations\AbstractMigration;
 /**
  * Auto-generated Migration: Please modify to your needs!
  */
-final class Version20240424072545 extends AbstractMigration
+final class Version20240426064235 extends AbstractMigration
 {
     public function getDescription(): string
     {
diff --git a/src/Controller/NewsController.php b/src/Controller/NewsController.php
new file mode 100644
index 0000000..ae4ea9f
--- /dev/null
+++ b/src/Controller/NewsController.php
@@ -0,0 +1,55 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Controller;
+
+use App\Requests\NewsListRequest;
+use Exception;
+use App\Service\NewsService;
+use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\Attribute\Route;
+
+#[Route("/api/v1")]
+class NewsController extends AbstractController
+{
+    public function __construct(private NewsService $newsService) {}
+
+    #[Route('/news', name: 'news', methods: ['GET'])]
+    public function news(NewsListRequest $request): Response
+    {
+        $page = $request->getRequest()->query->get('page');
+        $limit = $request->getRequest()->query->get('limit');
+        $newsCategory = $request->getRequest()->query->get('news_category');
+        $news = $this->newsService->getNews($page, $limit, $newsCategory);
+        return $this->json($news);
+    }
+
+    #[Route('/news/mainNews', name: 'mainNews', methods: ['GET'])]
+    public function mainNews(): Response
+    {
+        $mainNews = $this->newsService->getMainNews();
+        return $this->json($mainNews);
+    }
+
+    #[Route('/news/search', name: 'newsSearch', methods: ['GET'])]
+    public function newsSearch(): Response
+    {
+        $newsSearch = $this->newsService->getNewsSearch();
+        return $this->json($newsSearch);
+    }
+
+    #[Route('/news/{newsId}', name: 'newsOne', methods: ['GET'])]
+    public function newsOne(Request $request): Response
+    {
+        try {
+            $newsId = $request->get('newsId');;
+            $news = $this->newsService->getNewsOne($newsId);
+            return $this->json($news);
+        } catch (Exception $e) {
+            return new Response($e->getMessage(), $e->getCode());
+        }
+    }
+}
diff --git a/src/Controller/RestaurantController.php b/src/Controller/RestaurantController.php
index 9f665fc..d6f3983 100644
--- a/src/Controller/RestaurantController.php
+++ b/src/Controller/RestaurantController.php
@@ -4,6 +4,8 @@ declare(strict_types=1);
 
 namespace App\Controller;
 
+use App\Requests\RestaurantListRequest;
+use Exception;
 use App\Service\RestaurantService;
 use Nelmio\ApiDocBundle\Annotation\Model;
 use OpenApi\Attributes as OA;
@@ -13,6 +15,7 @@ use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\Routing\Attribute\Route;
 use App\Model\RestaurantList;
+use Symfony\Component\Serializer\SerializerInterface;
 
 #[Route("/api/v1")]
 class RestaurantController extends AbstractController
@@ -46,12 +49,12 @@ class RestaurantController extends AbstractController
         schema: new Schema(type: "integer")
     )]
     #[Model(type: RestaurantList::class)]
-    public function restaurants(Request $request): Response
+    public function restaurants(RestaurantListRequest $request): Response
     {
-        $page = $request->query->getInt('page');
-        $limit = $request->query->getInt('limit');
-        $restaurantTypeId = $request->query->getInt('restaurant_type_id');
-        $kitchenId = $request->query->getInt('kitchen_id');
+        $page = $request->getRequest()->query->get('page');
+        $limit = $request->getRequest()->query->get('limit');
+        $restaurantTypeId = $request->getRequest()->query->get('restaurant_type_id');
+        $kitchenId = $request->getRequest()->query->get('kitchen_id');
         $restaurantsList = $this->restaurantService->getRestaurants(
             $page, $limit, $restaurantTypeId, $kitchenId
         );
@@ -59,10 +62,19 @@ class RestaurantController extends AbstractController
     }
 
     #[Route('/restaurants/{restaurantId}', name: 'restaurant', methods: ['GET'])]
-    public function restaurant(Request $request): Response
+    public function restaurant(Request $request, SerializerInterface $serializer): Response
     {
-        $restaurantId = (int)$request->get('restaurantId');
-        $restaurant = $this->restaurantService->getRestaurant($restaurantId);
-        return $this->json($restaurant);
+        try {
+            $restaurantId = (int)$request->get('restaurantId');
+            $restaurant = $this->restaurantService->getRestaurant($restaurantId);
+            $json = $serializer->serialize($restaurant, 'json', [
+                'circular_reference_handler' => function ($object) {
+                    return $object->getId();
+                }
+            ]);
+            return new Response($json, Response::HTTP_OK, ['Content-Type' => 'application/json']);
+        } catch (Exception $e) {
+            return new Response($e->getMessage(), $e->getCode());
+        }
     }
 }
diff --git a/src/Entity/News.php b/src/Entity/News.php
index 092b75e..3b588b2 100644
--- a/src/Entity/News.php
+++ b/src/Entity/News.php
@@ -53,7 +53,7 @@ class News
     /**
      * @var Collection<int, NewsCategory>
      */
-    #[ORM\ManyToMany(targetEntity: NewsCategory::class)]
+    #[ORM\ManyToMany(targetEntity: NewsCategory::class, inversedBy: "newsCategories")]
     private Collection $newsCategories;
 
     public function __construct()
diff --git a/src/Entity/NewsCategory.php b/src/Entity/NewsCategory.php
index 6190e58..fa3a554 100644
--- a/src/Entity/NewsCategory.php
+++ b/src/Entity/NewsCategory.php
@@ -20,6 +20,9 @@ class NewsCategory
     #[ORM\Column(length: 255)]
     private ?string $code = null;
 
+    #[ORM\ManyToMany(targetEntity: News::class, mappedBy: 'news')]
+    private Collection $news;
+
     public function getId(): ?Uuid
     {
         return $this->id;
diff --git a/src/Entity/Restaurant.php b/src/Entity/Restaurant.php
index 02fd3b1..a743f1b 100644
--- a/src/Entity/Restaurant.php
+++ b/src/Entity/Restaurant.php
@@ -41,11 +41,11 @@ class Restaurant
     #[ORM\Column(type: Types::ARRAY)]
     private array $coordinates = [];
 
-    #[ORM\ManyToOne(inversedBy: 'restaurants')]
+    #[ORM\ManyToOne(fetch: 'EAGER', inversedBy: 'restaurants')]
     #[ORM\JoinColumn(nullable: false)]
     private ?RestaurantType $typeId = null;
 
-    #[ORM\ManyToOne(inversedBy: 'restaurants')]
+    #[ORM\ManyToOne(fetch: 'EAGER', inversedBy: 'restaurants')]
     #[ORM\JoinColumn(nullable: false)]
     private ?Settlement $settlementId = null;
 
@@ -67,7 +67,7 @@ class Restaurant
     /**
      * @var Collection<int, Phone>
      */
-    #[ORM\OneToMany(targetEntity: Phone::class, mappedBy: 'restaurant')]
+    #[ORM\OneToMany(targetEntity: Phone::class,  mappedBy: 'restaurant')]
     private Collection $phone;
 
     /**
diff --git a/src/Exception/NewsExceptionEnum.php b/src/Exception/NewsExceptionEnum.php
new file mode 100644
index 0000000..6535eff
--- /dev/null
+++ b/src/Exception/NewsExceptionEnum.php
@@ -0,0 +1,8 @@
+<?php
+
+namespace App\Exception;
+
+enum NewsExceptionEnum: int
+{
+    case NotFound = 404;
+}
diff --git a/src/Exception/RestaurantExceptionEnum.php b/src/Exception/RestaurantExceptionEnum.php
new file mode 100644
index 0000000..fe3469d
--- /dev/null
+++ b/src/Exception/RestaurantExceptionEnum.php
@@ -0,0 +1,8 @@
+<?php
+
+namespace App\Exception;
+
+enum RestaurantExceptionEnum: int
+{
+    case NotFound = 404;
+}
diff --git a/src/Mapper/NewsMapper.php b/src/Mapper/NewsMapper.php
new file mode 100644
index 0000000..65cf34d
--- /dev/null
+++ b/src/Mapper/NewsMapper.php
@@ -0,0 +1,93 @@
+<?php
+
+namespace App\Mapper;
+
+use App\Entity\News;
+use App\Entity\NewsCategory;
+use App\Model\File;
+use App\Model\NewsDetailElement;
+use App\Model\NewsFilterVariants;
+use App\Model\NewsList;
+use App\Model\NewsListingElement;
+use App\Model\NewsCategory as NewsCategoryModel;
+use App\Model\Pagination;
+use Ramsey\Collection\Collection;
+
+class NewsMapper
+{
+    public static function mapToNewsList(
+        $news,
+        $newsCategory,
+        $page,
+        $limit,
+        $count): NewsList
+    {
+        return new NewsList(
+            new Pagination($page, ceil($count / $limit), $limit),
+            new Collection(NewsListingElement::class, array_map(
+                function (News $newsOne) {
+                    return self::mapToListingElement($newsOne);
+                }, $news)
+            ),
+            new NewsFilterVariants(
+                new Collection(NewsCategoryModel::class, array_map(
+                    function (NewsCategory $newsCategoryOne) {
+                        return self::mapToNewsCategory($newsCategoryOne);
+                    }, $newsCategory)
+                )
+            )
+        );
+    }
+
+    public static function mapToListingElement(News $newsOne): NewsListingElement
+    {
+        $file = new File(
+            1,
+            "asd",
+            "Краткое описание",
+            1024,
+            "png",
+            "/upload/asd.png"
+        );
+        return new NewsListingElement(
+            $newsOne->getId(),
+            $newsOne->getPreviewText(),
+            $newsOne->getDetailText(),
+            $file,
+            $newsOne->getCreateAt()->format('d.m.Y'),
+            "/{$newsOne->getCode()}/code/"
+        );
+    }
+
+    public static function mapToDetailElement(News $newsOne): NewsDetailElement
+    {
+        $file = new File(
+            1,
+            "asd",
+            "Краткое описание",
+            1024,
+            "png",
+            "/upload/asd.png"
+        );
+        return new NewsDetailElement(
+            $newsOne->getId(),
+            $newsOne->getPreviewText(),
+            $newsOne->getDetailText(),
+            $newsOne->getDetailText(),
+            $file,
+            $newsOne->getCreateAt()->format('d.m.Y'),
+            "Отель «Арктика»",
+            "otel-arktika",
+            "otel-arktika"
+        );
+    }
+
+    public static function mapToNewsCategory(NewsCategory $newsCategory): NewsCategoryModel
+    {
+        return new NewsCategoryModel(
+            $newsCategory->getId(),
+            $newsCategory->getName(),
+            $newsCategory->getCode(),
+        );
+    }
+}
\ No newline at end of file
diff --git a/src/Mapper/RestaurantMapper.php b/src/Mapper/RestaurantMapper.php
index 5f9b6ae..62bc18a 100644
--- a/src/Mapper/RestaurantMapper.php
+++ b/src/Mapper/RestaurantMapper.php
@@ -11,7 +11,10 @@ use App\Entity\RestaurantType;
 use App\Entity\Tags;
 use App\Model\File;
 use App\Model\KitchenType;
+use App\Model\Pagination;
 use App\Model\RestaurantDetailElement;
+use App\Model\RestaurantFilterVariants;
+use App\Model\RestaurantList;
 use App\Model\RestaurantListingElement;
 use App\Model\RestaurantType as RestaurantTypeModel;
 use App\Model\Tag;
@@ -19,17 +22,46 @@ use Ramsey\Collection\Collection;
 
 class RestaurantMapper
 {
+    public static function mapToRestaurantList(
+        $restaurants,
+        $restaurantTypes,
+        $kitchens,
+        $page,
+        $limit,
+        $count): RestaurantList
+    {
+        return new RestaurantList(
+            new Pagination($page, ceil($count / $limit), $limit),
+            new Collection(RestaurantListingElement::class, array_map(
+                function (Restaurant $restaurant) {
+                    return RestaurantMapper::mapToListElement($restaurant);
+                }, $restaurants)),
+            new RestaurantFilterVariants(
+                new Collection(
+                    RestaurantTypeModel::class, array_map(
+                        function (RestaurantType $restaurantType) {
+                            return RestaurantMapper::mapToRestaurantType($restaurantType);
+                        }, $restaurantTypes
+                    )
+                ),
+                new Collection(
+                    KitchenType::class, array_map(
+                    function (Kitchen $kitchen) {
+                        return RestaurantMapper::mapToKitchenType($kitchen);
+                    }, $kitchens
+                ),
+                )
+            )
+        );
+    }
+
     public static function mapToListElement(Restaurant $restaurant): RestaurantListingElement
     {
         return new RestaurantListingElement(
             $restaurant->getId(),
             $restaurant->getName(),
             $restaurant->getCode(),
-            new RestaurantTypeModel(
-                $restaurant->getTypeId()->getId(),
-                $restaurant->getTypeId()->getName(),
-                $restaurant->getTypeId()->getCode()
-            ),
+            self::mapToRestaurantType($restaurant->getTypeId()),
             $restaurant->getCheckInfo(),
             new File(
                 1,
@@ -58,7 +90,7 @@ class RestaurantMapper
             $restaurant->getName(),
             $restaurant->getCode(),
             implode(',', $restaurant->getCoordinates()),
-            //self::mapToRestaurantType($restaurant->getTypeId()),
+            self::mapToRestaurantType($restaurant->getTypeId()),
             $restaurant->getCheckPrice(),
             $restaurant->getCheckInfo(),
             new Collection(
@@ -67,14 +99,26 @@ class RestaurantMapper
                     return self::mapToKitchenType($kitchen);
                 }, $restaurant->getKitchen()->toArray()),
             ),
-            new Collection(Phone::class, $restaurant->getPhone()->toArray()),
-            new Collection(Email::class, $restaurant->getEmail()->toArray()),
-            new Collection(Address::class, $restaurant->getAddress()->toArray()),
+            new Collection("string", array_map(
+                function (Phone $phone) {
+                    return self::mapToPhone($phone);
+                }, $restaurant->getPhone()->toArray()
+            )),
+            new Collection("string", array_map(
+                function (Email $email) {
+                    return self::mapToEmail($email);
+                }, $restaurant->getEmail()->toArray()
+            )),
+            new Collection("string", array_map(
+                function (Address $address) {
+                    return self::mapToAddress($address);
+                }, $restaurant->getAddress()->toArray()
+            )),
             new Collection(Tag::class, array_map(
                 function (Tags $tag) {
                     return new Tag(
                         "группа тегов 1",
-                        new Collection($tag->getName()),
+                        new Collection("string", [$tag->getName()]),
                     );
                 }, $restaurant->getTags()->toArray()
             )),
@@ -103,4 +147,19 @@ class RestaurantMapper
             $kitchen->getName()
         );
     }
+
+    public static function mapToPhone(Phone $phone): string
+    {
+        return $phone->getName();
+    }
+
+    public static function mapToEmail(Email $email): string
+    {
+        return $email->getName();
+    }
+
+    public static function mapToAddress(Address $address): string
+    {
+        return $address->getName();
+    }
 }
\ No newline at end of file
diff --git a/src/Model/NewsCategory.php b/src/Model/NewsCategory.php
index ba1e1a5..e22f456 100644
--- a/src/Model/NewsCategory.php
+++ b/src/Model/NewsCategory.php
@@ -2,21 +2,23 @@
 
 namespace App\Model;
 
+use Symfony\Component\Uid\Uuid;
+
 class NewsCategory
 {
-    private int $id;
+    private Uuid $id;
     private string $name;
     private string $code;
 
 
-    public function __construct(int $id, string $name, string $code)
+    public function __construct(Uuid $id, string $name, string $code)
     {
         $this->id = $id;
         $this->name = $name;
         $this->code = $code;
     }
 
-    public function getId(): int
+    public function getId(): Uuid
     {
         return $this->id;
     }
diff --git a/src/Model/NewsDetailElement.php b/src/Model/NewsDetailElement.php
index 99b3453..c125aae 100644
--- a/src/Model/NewsDetailElement.php
+++ b/src/Model/NewsDetailElement.php
@@ -2,9 +2,11 @@
 
 namespace App\Model;
 
+use Symfony\Component\Uid\Uuid;
+
 class NewsDetailElement
 {
-    private int $id;
+    private Uuid $id;
     private string $name;
     private string $description;
     private string $text;
@@ -15,7 +17,7 @@ class NewsDetailElement
     private string $seoKeywords;
 
     public function __construct(
-        int    $id,
+        Uuid   $id,
         string $name,
         string $description,
         string $text,
@@ -36,7 +38,7 @@ class NewsDetailElement
         $this->seoKeywords = $seoKeywords;
     }
 
-    public function getId(): int
+    public function getId(): Uuid
     {
         return $this->id;
     }
diff --git a/src/Model/NewsListingElement.php b/src/Model/NewsListingElement.php
index 5134307..dd1b0a0 100644
--- a/src/Model/NewsListingElement.php
+++ b/src/Model/NewsListingElement.php
@@ -2,9 +2,11 @@
 
 namespace App\Model;
 
+use Symfony\Component\Uid\Uuid;
+
 class NewsListingElement
 {
-    private int $id;
+    private Uuid $id;
     private string $name;
     private string $description;
     private File $image;
@@ -12,7 +14,7 @@ class NewsListingElement
     private string $detailLink;
 
     public function __construct(
-        int $id,
+        Uuid $id,
         string $name,
         string $description,
         File $image,
@@ -27,7 +29,7 @@ class NewsListingElement
         $this->detailLink = $detailLink;
     }
 
-    public function getId(): int
+    public function getId(): Uuid
     {
         return $this->id;
     }
diff --git a/src/Model/RestaurantDetailElement.php b/src/Model/RestaurantDetailElement.php
index 01e3e67..face2bf 100644
--- a/src/Model/RestaurantDetailElement.php
+++ b/src/Model/RestaurantDetailElement.php
@@ -10,7 +10,7 @@ class RestaurantDetailElement
     private string $name;
     private string $code;
     private string $coordinates;
-    //private RestaurantType $type;
+    private RestaurantType $type;
     private string $check;
     private string $checkInfo;
     /**
@@ -48,7 +48,7 @@ class RestaurantDetailElement
         string $name,
         string $code,
         string $coordinates,
-        //RestaurantType $type,
+        RestaurantType $type,
         string $check,
         string $checkInfo,
         Collection $kitchen,
@@ -67,7 +67,7 @@ class RestaurantDetailElement
         $this->name = $name;
         $this->code = $code;
         $this->coordinates = $coordinates;
-        //$this->type = $type;
+        $this->type = $type;
         $this->check = $check;
         $this->checkInfo = $checkInfo;
         $this->kitchen = $kitchen;
@@ -103,10 +103,10 @@ class RestaurantDetailElement
         return $this->coordinates;
     }
 
-//    public function getType(): RestaurantType
-//    {
-//        return $this->type;
-//    }
+    public function getType(): RestaurantType
+    {
+        return $this->type;
+    }
 
     public function getCheck(): string
     {
diff --git a/src/Repository/Interface/NewsCategoryRepositoryInterface.php b/src/Repository/Interface/NewsCategoryRepositoryInterface.php
new file mode 100644
index 0000000..c2d7e4d
--- /dev/null
+++ b/src/Repository/Interface/NewsCategoryRepositoryInterface.php
@@ -0,0 +1,8 @@
+<?php
+
+namespace App\Repository\Interface;
+
+interface NewsCategoryRepositoryInterface
+{
+    public function getAll(): array;
+}
\ No newline at end of file
diff --git a/src/Repository/Interface/NewsRepositoryInterface.php b/src/Repository/Interface/NewsRepositoryInterface.php
new file mode 100644
index 0000000..7ad6644
--- /dev/null
+++ b/src/Repository/Interface/NewsRepositoryInterface.php
@@ -0,0 +1,13 @@
+<?php
+
+namespace App\Repository\Interface;
+
+use App\Entity\News;
+
+interface NewsRepositoryInterface
+{
+    public function getAll(int $page, int $limit, int $newsCategory): array;
+    public function getCount(): int;
+    public function getMainNews(): News;
+    public function getNewsById(string $newsId): News|null;
+}
\ No newline at end of file
diff --git a/src/Repository/Interface/RestaurantRepositoryInterface.php b/src/Repository/Interface/RestaurantRepositoryInterface.php
index 6bffa21..062d716 100644
--- a/src/Repository/Interface/RestaurantRepositoryInterface.php
+++ b/src/Repository/Interface/RestaurantRepositoryInterface.php
@@ -6,9 +6,9 @@ use App\Entity\Restaurant;
 
 interface RestaurantRepositoryInterface
 {
-    public function getAll($page, $limit, $restaurantTypeId, $kitchenId): array;
+    public function getAll(int $page, int $limit, int $restaurantTypeId, int $kitchenId): array;
 
     public function getCount(): int;
 
-    public function getById(int $id): Restaurant;
+    public function getById(int $id): Restaurant|null;
 }
\ No newline at end of file
diff --git a/src/Repository/NewsCategoryRepository.php b/src/Repository/NewsCategoryRepository.php
index 3a5eeeb..0b0308b 100644
--- a/src/Repository/NewsCategoryRepository.php
+++ b/src/Repository/NewsCategoryRepository.php
@@ -3,6 +3,7 @@
 namespace App\Repository;
 
 use App\Entity\NewsCategory;
+use App\Repository\Interface\NewsCategoryRepositoryInterface;
 use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
 use Doctrine\Persistence\ManagerRegistry;
 
@@ -14,35 +15,15 @@ use Doctrine\Persistence\ManagerRegistry;
  * @method NewsCategory[]    findAll()
  * @method NewsCategory[]    findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
  */
-class NewsCategoryRepository extends ServiceEntityRepository
+class NewsCategoryRepository extends ServiceEntityRepository implements NewsCategoryRepositoryInterface
 {
     public function __construct(ManagerRegistry $registry)
     {
         parent::__construct($registry, NewsCategory::class);
     }
 
-//    /**
-//     * @return NewsCategory[] Returns an array of NewsCategory objects
-//     */
-//    public function findByExampleField($value): array
-//    {
-//        return $this->createQueryBuilder('n')
-//            ->andWhere('n.exampleField = :val')
-//            ->setParameter('val', $value)
-//            ->orderBy('n.id', 'ASC')
-//            ->setMaxResults(10)
-//            ->getQuery()
-//            ->getResult()
-//        ;
-//    }
-
-//    public function findOneBySomeField($value): ?NewsCategory
-//    {
-//        return $this->createQueryBuilder('n')
-//            ->andWhere('n.exampleField = :val')
-//            ->setParameter('val', $value)
-//            ->getQuery()
-//            ->getOneOrNullResult()
-//        ;
-//    }
+    public function getAll(): array
+    {
+        return $this->findAll();
+    }
 }
diff --git a/src/Repository/NewsRepository.php b/src/Repository/NewsRepository.php
index 3b5d864..011be28 100644
--- a/src/Repository/NewsRepository.php
+++ b/src/Repository/NewsRepository.php
@@ -2,7 +2,10 @@
 
 namespace App\Repository;
 
+use App\Exception\NewsExceptionEnum;
+use Exception;
 use App\Entity\News;
+use App\Repository\Interface\NewsRepositoryInterface;
 use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
 use Doctrine\Persistence\ManagerRegistry;
 
@@ -14,35 +17,43 @@ use Doctrine\Persistence\ManagerRegistry;
  * @method News[]    findAll()
  * @method News[]    findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
  */
-class NewsRepository extends ServiceEntityRepository
+class NewsRepository extends ServiceEntityRepository implements NewsRepositoryInterface
 {
     public function __construct(ManagerRegistry $registry)
     {
         parent::__construct($registry, News::class);
     }
 
-//    /**
-//     * @return News[] Returns an array of News objects
-//     */
-//    public function findByExampleField($value): array
-//    {
-//        return $this->createQueryBuilder('n')
-//            ->andWhere('n.exampleField = :val')
-//            ->setParameter('val', $value)
-//            ->orderBy('n.id', 'ASC')
-//            ->setMaxResults(10)
-//            ->getQuery()
-//            ->getResult()
-//        ;
-//    }
+    public function getAll($page, $limit, $newsCategory): array
+    {
+        $query = $this->getEntityManager()->createQueryBuilder();
+        $query->select('n')->from(News::class, 'n');
+        if ($newsCategory !== null) {
+            $query->join('n.newsCategories', 'nc')
+                ->andWhere('nc.id = :newsCategory')
+                ->setParameter('newsCategory', $newsCategory);
+        }
+        $query->setMaxResults($limit)
+            ->setFirstResult(($page - 1) * $limit);
+        return $query->getQuery()->getResult();
+    }
+
+    public function getCount(): int
+    {
+        return $this->count();
+    }
 
-//    public function findOneBySomeField($value): ?News
-//    {
-//        return $this->createQueryBuilder('n')
-//            ->andWhere('n.exampleField = :val')
-//            ->setParameter('val', $value)
-//            ->getQuery()
-//            ->getOneOrNullResult()
-//        ;
-//    }
+    public function getMainNews(): News
+    {
+        return $this->findOneBy(['mainPageRender' => true]);
+    }
+
+    public function getNewsById(string $newsId): News|null
+    {
+        try {
+            return $this->find(['id' => $newsId]);
+        } catch (Exception $e) {
+            throw new Exception("News not found", NewsExceptionEnum::NotFound->value);
+        }
+    }
 }
diff --git a/src/Repository/RestaurantRepository.php b/src/Repository/RestaurantRepository.php
index 53c0561..0f2061e 100644
--- a/src/Repository/RestaurantRepository.php
+++ b/src/Repository/RestaurantRepository.php
@@ -26,11 +26,11 @@ class RestaurantRepository extends ServiceEntityRepository implements Restaurant
     {
         $query = $this->createQueryBuilder('r');
         $query->select('r');
-        if ($restaurantTypeId !== 0) {
+        if ($restaurantTypeId !== null) {
             $query->andWhere('r.typeId = :restaurantTypeId')
                 ->setParameter('restaurantTypeId', $restaurantTypeId);
         }
-        if ($kitchenId !== 0) {
+        if ($kitchenId !== null) {
             $query->join('r.kitchen', 'k')
             ->andWhere('k.id = :kitchenId')
                 ->setParameter('kitchenId', $kitchenId);
@@ -45,7 +45,7 @@ class RestaurantRepository extends ServiceEntityRepository implements Restaurant
         return $this->count();
     }
 
-    public function getById(int $id): Restaurant
+    public function getById(int $id): Restaurant|null
     {
         return $this->find($id);
     }
diff --git a/src/Requests/NewsListRequest.php b/src/Requests/NewsListRequest.php
new file mode 100644
index 0000000..914a3c8
--- /dev/null
+++ b/src/Requests/NewsListRequest.php
@@ -0,0 +1,28 @@
+<?php
+
+namespace App\Requests;
+
+use Symfony\Component\Form\Extension\Core\Type\UuidType;
+use Symfony\Component\Uid\UuidV4;
+use Symfony\Component\Validator\Constraints as Assert;
+
+class NewsListRequest extends BaseRequest
+{
+    #[Assert\Type('int')]
+    #[Assert\NotBlank]
+    public $page;
+    #[Assert\Type('int')]
+    #[Assert\NotBlank]
+    public $limit;
+    #[Assert\Uuid]
+    public $news_category;
+
+    protected function populate(): void
+    {
+        foreach ($this->getRequest()->query as $property => $value) {
+            if (property_exists($this, $property)) {
+                $this->{$property} = ctype_digit($value) ? (int)$value : $value;
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Requests/RestaurantListRequest.php b/src/Requests/RestaurantListRequest.php
new file mode 100644
index 0000000..701674d
--- /dev/null
+++ b/src/Requests/RestaurantListRequest.php
@@ -0,0 +1,28 @@
+<?php
+
+namespace App\Requests;
+
+use Symfony\Component\Validator\Constraints as Assert;
+
+class RestaurantListRequest extends BaseRequest
+{
+    #[Assert\Type('int')]
+    #[Assert\NotBlank]
+    public $page;
+    #[Assert\Type('int')]
+    #[Assert\NotBlank]
+    public $limit;
+    #[Assert\Type('int')]
+    public $restaurant_type_id;
+    #[Assert\Type('int')]
+    public $kitchen_id;
+
+    protected function populate(): void
+    {
+        foreach ($this->getRequest()->query as $property => $value) {
+            if (property_exists($this, $property)) {
+                $this->{$property} = ctype_digit($value) ? (int)$value : $value;
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Service/NewsService.php b/src/Service/NewsService.php
new file mode 100644
index 0000000..043d000
--- /dev/null
+++ b/src/Service/NewsService.php
@@ -0,0 +1,48 @@
+<?php
+
+namespace App\Service;
+
+use Exception;
+use App\Exception\NewsExceptionEnum;
+use App\Mapper\NewsMapper;
+use App\Model\NewsDetailElement;
+use App\Model\NewsList;
+use App\Model\NewsListingElement;
+use App\Repository\Interface\NewsCategoryRepositoryInterface;
+use App\Repository\Interface\NewsRepositoryInterface;
+
+class NewsService
+{
+    public function __construct(
+        private NewsRepositoryInterface $newsRepository,
+        private NewsCategoryRepositoryInterface $newsCategoryRepository) {}
+
+    public function getNews($page, $limit, $newsCategory): NewsList
+    {
+        $news = $this->newsRepository->getAll($page, $limit, $newsCategory);
+        $newsCategories = $this->newsCategoryRepository->getAll();
+        $count = $this->newsRepository->getCount();
+        return NewsMapper::mapToNewsList($news, $newsCategories, $page, $limit, $count);
+    }
+
+    public function getMainNews(): NewsListingElement
+    {
+        $mainNews = $this->newsRepository->getMainNews();
+        return NewsMapper::mapToListingElement($mainNews);
+    }
+
+    public function getNewsSearch(): NewsDetailElement
+    {
+        $mainNews = $this->newsRepository->getMainNews();
+        return NewsMapper::mapToDetailElement($mainNews);
+    }
+
+    public function getNewsOne($newsId): NewsDetailElement
+    {
+        $news = $this->newsRepository->getNewsById($newsId);
+        if ($news == null) {
+            throw new Exception("News not found", NewsExceptionEnum::NotFound->value);
+        }
+        return NewsMapper::mapToDetailElement($news);
+    }
+}
\ No newline at end of file
diff --git a/src/Service/RestaurantService.php b/src/Service/RestaurantService.php
index 7e1866d..8f1a42a 100644
--- a/src/Service/RestaurantService.php
+++ b/src/Service/RestaurantService.php
@@ -2,26 +2,14 @@
 
 namespace App\Service;
 
-use App\Entity\Address;
-use App\Entity\Email;
-use App\Entity\Kitchen;
-use App\Entity\Phone;
-use App\Entity\Restaurant;
-use App\Entity\RestaurantType;
-use App\Entity\Tags;
+use App\Exception\RestaurantExceptionEnum;
+use Exception;
 use App\Mapper\RestaurantMapper;
-use App\Model\File;
-use App\Model\KitchenType;
-use App\Model\Pagination;
 use App\Model\RestaurantDetailElement;
-use App\Model\RestaurantFilterVariants;
 use App\Model\RestaurantList;
-use App\Model\RestaurantListingElement;
-use App\Model\RestaurantType as RestaurantTypeModel;
 use App\Repository\Interface\KitchenRepositoryInterface;
 use App\Repository\Interface\RestaurantRepositoryInterface;
 use App\Repository\Interface\RestaurantTypeRepositoryInterface;
-use Ramsey\Collection\Collection;
 
 class RestaurantService
 {
@@ -45,34 +33,22 @@ class RestaurantService
         $count = $this->restaurantRepository->getCount();
         $restaurantTypes = $this->restaurantTypeRepository->getAll();
         $kitchens = $this->kitchenRepository->getAll();
-        return new RestaurantList(
-            new Pagination($page, ceil($count / $limit), $limit),
-            new Collection(RestaurantListingElement::class, array_map(
-            function (Restaurant $restaurant) {
-                return RestaurantMapper::mapToListElement($restaurant);
-            }, $restaurants)),
-            new RestaurantFilterVariants(
-                new Collection(
-                    RestaurantTypeModel::class, array_map(
-                        function (RestaurantType $restaurantType) {
-                            return RestaurantMapper::mapToRestaurantType($restaurantType);
-                        }, $restaurantTypes
-                    )
-                ),
-                new Collection(
-                    KitchenType::class, array_map(
-                        function (Kitchen $kitchen) {
-                            return RestaurantMapper::mapToKitchenType($kitchen);
-                        }, $kitchens
-                    ),
-                )
-            )
+        return RestaurantMapper::mapToRestaurantList(
+            $restaurants,
+            $restaurantTypes,
+            $kitchens,
+            $page,
+            $limit,
+            $count
         );
     }
 
     public function getRestaurant(int $id): RestaurantDetailElement
     {
         $restaurant = $this->restaurantRepository->getById($id);
+        if ($restaurant == null) {
+            throw new Exception("Restaurant not found", RestaurantExceptionEnum::NotFound->value);
+        }
         return RestaurantMapper::mapToDetailElement($restaurant);
     }
 }
\ No newline at end of file
-- 
GitLab