src/Controller/ProfileListController.php line 2286

Open in your IDE?
  1. <?php
  2. /**
  3.  * Created by simpson <simpsonwork@gmail.com>
  4.  * Date: 2019-03-19
  5.  * Time: 22:28
  6.  */
  7. namespace App\Controller;
  8. use App\Attribute\SplitFirstPagePagination;
  9. use App\Bridge\Porpaginas\Doctrine\ORM\FakeORMQueryPage;
  10. use App\Dto\ProfileListSpecification;
  11. use App\Entity\Location\City;
  12. use App\Entity\Location\County;
  13. use App\Entity\Location\District;
  14. use App\Entity\Location\Station;
  15. use App\Entity\Profile\BodyTypes;
  16. use App\Entity\Profile\BreastTypes;
  17. use App\Entity\Profile\Genders;
  18. use App\Entity\Profile\HairColors;
  19. use App\Entity\Profile\Nationalities;
  20. use App\Entity\Profile\ProfileInventoryItem;
  21. use App\Entity\Profile\ProfileInventoryItemType;
  22. use App\Entity\Profile\PrivateHaircuts;
  23. use App\Entity\Service;
  24. use App\Entity\ServiceGroups;
  25. use App\Entity\TakeOutLocations;
  26. use App\Repository\ServiceRepository;
  27. use App\Repository\StationRepository;
  28. use App\Service\CountryCurrencyResolver;
  29. use App\Service\DefaultCityProvider;
  30. use App\Service\Features;
  31. use App\Service\HomepageCityListingsBlockProvider;
  32. use App\Service\ListingRotationApi;
  33. use App\Service\ListingService;
  34. use App\Service\ProfileList;
  35. use App\Service\ProfileListingAppliedFiltersResolver;
  36. use App\Service\ProfileListingRecommendationsFiller;
  37. use App\Service\ProfileListingDataCreator;
  38. use App\Service\ProfileListSpecificationService;
  39. use App\Service\ProfileFilterService;
  40. use App\Service\ProfileTopBoard;
  41. use App\Service\Top100ProfilesService;
  42. use App\Specification\ElasticSearch\ISpecification;
  43. use App\Specification\Profile\ProfileHasApartments;
  44. use App\Specification\Profile\ProfileHasComments;
  45. use App\Specification\Profile\ProfileHasVideo;
  46. use App\Specification\Profile\ProfileIdIn;
  47. use App\Specification\Profile\ProfileIdINOrderedByINValues;
  48. use App\Specification\Profile\ProfileIdNotIn;
  49. use App\Specification\Profile\ProfileIsApproved;
  50. use App\Specification\Profile\ProfileIsElite;
  51. use App\Specification\Profile\ProfileIsLocated;
  52. use App\Specification\Profile\ProfileIsProvidingOneOfServices;
  53. use App\Specification\Profile\ProfileIsProvidingTakeOut;
  54. use App\Specification\Profile\ProfileWithBodyType;
  55. use App\Specification\Profile\ProfileWithBreastType;
  56. use App\Specification\Profile\ProfileWithHairColor;
  57. use App\Specification\Profile\ProfileWithNationality;
  58. use App\Specification\Profile\ProfileWithPrivateHaircut;
  59. use Flagception\Bundle\FlagceptionBundle\Annotations\Feature;
  60. use Happyr\DoctrineSpecification\Filter\Filter;
  61. use Happyr\DoctrineSpecification\Logic\OrX;
  62. use OpenApi\Attributes as OA;
  63. use Porpaginas\Doctrine\ORM\ORMQueryResult;
  64. use Porpaginas\Page;
  65. use Psr\Cache\CacheItemPoolInterface;
  66. use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;
  67. use Sensio\Bundle\FrameworkExtraBundle\Configuration\Entity;
  68. use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
  69. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  70. use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
  71. use Symfony\Component\HttpFoundation\JsonResponse;
  72. use Symfony\Component\HttpFoundation\Request;
  73. use Happyr\DoctrineSpecification\Spec;
  74. use Symfony\Component\HttpFoundation\RequestStack;
  75. use Symfony\Component\HttpFoundation\Response;
  76. use Symfony\Component\Routing\Annotation\Route;
  77. use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
  78. /**
  79.  * @see \App\Console\Export\ExportRotationListingsConfigCommand for listing API endpoints
  80.  */
  81. #[Cache(maxage: 60, public: true)]
  82. class ProfileListController extends AbstractController
  83. {
  84.     use ExtendedPaginationTrait;
  85.     use SpecTrait;
  86.     use ProfileMinPriceTrait;
  87.     use ResponseTrait;
  88.     const ENTRIES_ON_PAGE = 36;
  89.     const RESULT_SOURCE_COUNTY = 'county';
  90.     const RESULT_SOURCE_DISTRICT = 'district';
  91.     const RESULT_SOURCE_STATION = 'station';
  92.     const RESULT_SOURCE_APPROVED = 'approved';
  93.     const RESULT_SOURCE_WITH_COMMENTS = 'with_comments';
  94.     const RESULT_SOURCE_WITH_VIDEO = 'with_video';
  95.     const RESULT_SOURCE_WITH_SELFIE = 'with_selfie';
  96.     const RESULT_SOURCE_TOP_100 = 'top_100';
  97.     const RESULT_SOURCE_ELITE = 'elite';
  98.     const RESULT_SOURCE_MASSEURS = 'masseurs';
  99.     const RESULT_SOURCE_MASSAGE_SERVICE = 'massage_service';
  100.     const RESULT_SOURCE_BY_PARAMS = 'by_params';
  101.     const RESULT_SOURCE_SERVICE = 'service';
  102.     const RESULT_SOURCE_PROFILE_INVENTORY_ITEM = 'profile_inventory_item';
  103.     const RESULT_SOURCE_CITY = 'city';
  104.     const RESULT_SOURCE_COUNTRY = 'country';
  105.     const RESULT_SOURCE_WITH_WHATSAPP = 'with_whatsapp';
  106.     const RESULT_SOURCE_WITH_TELEGRAM = 'with_telegram';
  107.     const RESULT_SOURCE_EIGHTEEN_YEARS_OLD = 'eighteen_years_old';
  108.     const RESULT_SOURCE_BIG_ASS = 'big_ass';
  109.     const RESULT_SOURCE_WITH_TATTOO = 'with_tattoo';
  110.     const RESULT_SOURCE_WITH_PIERCING = 'with_piercing';
  111.     const RESULT_SOURCE_ROUND_THE_CLOCK = 'round_the_clock';
  112.     const RESULT_SOURCE_FOR_TWO_HOURS = 'for_two_hours';
  113.     const RESULT_SOURCE_FOR_HOUR = 'for_hour';
  114.     const RESULT_SOURCE_EXPRESS_PROGRAM = 'express_program';
  115.     const RESULT_SOURCE_EROMASSAGE = 'eromassage';
  116.     const RESULT_SOURCE_VERIFIED = 'verified';
  117.     const RESULT_SOURCE_CHEAP = 'cheap';
  118.     const RESULT_SOURCE_MATURE = 'mature';
  119.     const RESULT_SOURCE_UZBEK = 'uzbek';
  120.     private ?string $source = null;
  121.     public function __construct(
  122.         private RequestStack $requestStack,
  123.         private ProfileList                     $profileList,
  124.         private CountryCurrencyResolver         $countryCurrencyResolver,
  125.         private ServiceRepository               $serviceRepository,
  126.         private ListingService                  $listingService,
  127.         private Features                        $features,
  128.         private ProfileFilterService            $profilesFilterService,
  129.         private ProfileListSpecificationService $profileListSpecificationService,
  130.         private ProfileListingDataCreator       $profileListingDataCreator,
  131.         private Top100ProfilesService           $top100ProfilesService,
  132.         private CacheItemPoolInterface          $stationAddedProfilesCache,
  133.         private ParameterBagInterface           $parameterBag,
  134.         private ListingRotationApi              $listingRotationApi,
  135.         private ProfileTopBoard                 $profileTopBoard,
  136.         private HomepageCityListingsBlockProvider $homepageCityListingsBlockProvider,
  137.         private ProfileListingRecommendationsFiller $profileListingRecommendationsFiller,
  138.         private ProfileListingAppliedFiltersResolver $listingAppliedFiltersResolver,
  139.         private NormalizerInterface $normalizer,
  140.     ) {}
  141.     /**
  142.      * @param Page|array $result
  143.      */
  144.     protected function jsonListingResponse($result, City $city, ?ProfileListSpecification $specs = null): JsonResponse
  145.     {
  146.         $filters = $this->listingAppliedFiltersResolver->resolve($city, $specs);
  147.         if (is_array($result)) {
  148.             return $this->json(array_merge(['filters' => $filters], $result));
  149.         }
  150.         $normalized = $this->normalizer->normalize($result);
  151.         if (!is_array($normalized)) {
  152.             $normalized = ['data' => $normalized];
  153.         }
  154.         return $this->json(array_merge(['filters' => $filters], $normalized));
  155.     }
  156.     /**
  157.      * @Feature("has_masseurs")
  158.      */
  159.     #[ParamConverter("city", converter: "city_converter")]
  160.     #[Route("/api/profiles/listing/city/{city}/masseur", name: "api.profile_listing.masseur", methods: "GET", format: "json")]
  161.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  162.     #[OA\Tag(name: "ProfileListing")]
  163.     #[OA\Response(
  164.         response: 200,
  165.         description: '',
  166.         content: new OA\JsonContent(ref: "#/components/schemas/ProfileListingPage")
  167.     )]
  168.     public function listForMasseur(Request $request, City $city, ServiceRepository $serviceRepository): Response
  169.     {
  170.         $specs = $this->profileListSpecificationService->listForMasseur($city);
  171.         $response = new Response();
  172.         $massageGroupServices = $serviceRepository->findBy(['group' => ServiceGroups::MASSAGE]);
  173.         $alternativeSpec = $this->getORSpecForItemsArray([$massageGroupServices], function($item): ProfileIsProvidingOneOfServices {
  174.             return new ProfileIsProvidingOneOfServices($item);
  175.         });
  176.         $result = $this->paginatedListing($city, '/city/{city}/masseur', ['city' => $city->getId()], $specs->spec(), $alternativeSpec, self::RESULT_SOURCE_MASSAGE_SERVICE, $response);
  177.         if ($this->isApiRequest($request)) {
  178.             return $this->jsonListingResponse($result, $city, $specs);
  179.         }
  180.         return $this->render('ProfileList/list.html.twig', [
  181.             'profiles' => $result,
  182.             'source' => $this->source,
  183.             'source_default' => self::RESULT_SOURCE_MASSEURS,
  184.             'recommendationSpec' => $specs->recommendationSpec(),
  185.         ], response: $response);
  186.     }
  187.     /**
  188.      * @Feature("extra_category_big_ass")
  189.      */
  190.     #[ParamConverter("city", converter: "city_converter")]
  191.     #[Route("/api/profiles/listing/city/{city}/category/big_ass", name: "api.profile_listing.category.big_ass", methods: "GET", format: "json")]
  192.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  193.     #[OA\Tag(name: "ProfileListing")]
  194.     #[OA\Response(
  195.         response: 200,
  196.         description: '',
  197.         content: new OA\JsonContent(ref: "#/components/schemas/ProfileListingPage")
  198.     )]
  199.     public function listBigAss(Request $request, City $city): Response
  200.     {
  201.         $specs = $this->profileListSpecificationService->listBigAss();
  202.         $response = new Response();
  203.         $result = $this->paginatedListing($city, '/city/{city}/category/big_ass', ['city' => $city->getId()], $specs->spec(), null, null, $response);
  204.         if ($this->isApiRequest($request)) {
  205.             return $this->jsonListingResponse($result, $city, $specs);
  206.         }
  207.         return $this->render('ProfileList/list.html.twig', [
  208.             'profiles' => $result,
  209.             'source' => $this->source,
  210.             'source_default' => self::RESULT_SOURCE_BIG_ASS,
  211.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  212.                 'city' => $city->getUriIdentity(),
  213.                 'page' => $this->getCurrentPageNumber(),
  214.             ]),
  215.             'recommendationSpec' => $specs->recommendationSpec(),
  216.         ], response: $response);
  217.     }
  218.     public function listByDefaultCity(ParameterBagInterface $parameterBag, Request $request): Response
  219.     {
  220.         $controller = get_class($this).'::listByCity';
  221.         $path = [
  222.             'city' => $parameterBag->get('default_city'),
  223.             'subRequest' => true,
  224.         ];
  225.         //чтобы в обработчике можно было понять, по какому роуту зашли
  226.         $request->request->set('_route', 'profile_list.list_by_city');
  227.         return $this->forward($controller, $path);
  228.     }
  229.     private function paginatedListing(City $city, ?string $apiEndpoint, array $apiParams, ?Filter $listingSpec = null, ?OrX $alternativeSpec = null, ?string $alternativeSource = null, ?Response $response = null): Page
  230.     {
  231.         $topPlacement = $this->profileTopBoard->topPlacementSatisfiedBy($city, $listingSpec);
  232.         $topPlacement?->setTopCard(); // mark as top card for UI
  233.         $page = $this->getCurrentPageNumber();
  234.         $apiParams['city'] = $city->getId();
  235.         try {
  236.             if (null === $apiEndpoint) {
  237.                 throw new \RuntimeException('Empty API endpoint to switch to legacy listing query.');
  238.             }
  239.             $result = $this->listingRotationApi->paginate($apiEndpoint, $apiParams, $page, $topPlacement);
  240.             $response?->setMaxAge(10);
  241.         } catch (\Exception) {
  242.             $avoidOrTopPlacement = (null !== $topPlacement && $page < 2) ? $topPlacement : null;
  243.             $result = $this->profileList->list($city, null, $listingSpec, [], true, null, ProfileList::ORDER_BY_STATUS,
  244.                 null, true, null, [Genders::FEMALE], $avoidOrTopPlacement);
  245.         }
  246.         return $result;
  247.     }
  248.     #[SplitFirstPagePagination]
  249.     #[ParamConverter("city", converter: "city_converter")]
  250.     #[Route("/api/profiles/listing/city/{city}", name: "api.profile_listing.by_city", methods: "GET", format: "json")]
  251.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  252.     #[OA\Tag(name: "ProfileListing")]
  253.     #[OA\Response(
  254.         response: 200,
  255.         description: 'Листинг анкет по городу',
  256.         content: new OA\JsonContent(ref: "#/components/schemas/ProfileListingPage")
  257.     )]
  258.     public function listByCity(ParameterBagInterface $parameterBag, Request $request, City $city, bool $subRequest = false): Response
  259.     {
  260.         $page = $this->getCurrentPageNumber();
  261.         if (!$this->isApiRequest($request) && $this->features->redirect_default_city_to_homepage() && false === $subRequest && $city->equals($parameterBag->get('default_city')) && $page < 2) {
  262.             return $this->redirectToRoute('homepage', [], 301);
  263.         }
  264.         $specs = $this->profileListSpecificationService->listByCity();
  265.         $response = new Response();
  266.         $result = $this->paginatedListing($city, '/city/{city}', ['city' => $city->getId()], $specs->spec(), null, null, $response);
  267.         if ($this->isApiRequest($request)) {
  268.             return $this->jsonListingResponse($result, $city, $specs);
  269.         }
  270.         $homepageCityListingsBlock = null;
  271.         if ($this->shouldShowHomepageCityListingsBlock($city, $page, $subRequest)) {
  272.             $homepageCityListingsBlock = $this->homepageCityListingsBlockProvider->getForCity($city);
  273.         }
  274.         return $this->render('ProfileList/list.html.twig', [
  275.             'profiles' => $result,
  276.             'homepage_city_listings_block' => $homepageCityListingsBlock,
  277.             'recommendationSpec' => $specs->recommendationSpec(),
  278.         ], response: $response);
  279.     }
  280.     /**
  281.      * @Feature("intim_moscow_listing")
  282.      */
  283.     #[Cache(maxage: 3600, public: true)]
  284.     #[Route("/api/profiles/listing/city/{city}/intim", name: "api.profile_listing.intim", methods: "GET", format: "json")]
  285.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  286.     #[OA\Tag(name: "ProfileListing")]
  287.     #[OA\Response(
  288.         response: 200,
  289.         description: '',
  290.         content: new OA\JsonContent(ref: "#/components/schemas/ProfileListingPage")
  291.     )]
  292.     public function listIntim(Request $request, DefaultCityProvider $defaultCityProvider): Response
  293.     {
  294.         $city = $defaultCityProvider->getDefaultCity();
  295.         $request->attributes->set('city', $city);
  296.         $specs = $this->profileListSpecificationService->listByCity();
  297.         $response = new Response();
  298.         $result = $this->paginatedListing($city, '/city/{city}/intim', ['city' => $city->getId()], $specs->spec(), null, null, $response);
  299.         $result = $this->shuffleProfilesOnPage($result);
  300.         $response->setMaxAge(3600);
  301.         if ($this->isApiRequest($request)) {
  302.             return $this->jsonListingResponse($result, $city, $specs);
  303.         }
  304.         return $this->render('ProfileList/list.html.twig', [
  305.             'profiles' => $result,
  306.             'city' => $city,
  307.             'recommendationSpec' => $specs->recommendationSpec(),
  308.         ], response: $response);
  309.     }
  310.     #[ParamConverter("city", converter: "city_converter")]
  311.     #[Entity("county", expr: "repository.ofUriIdentityWithinCity(county, city)")]
  312.     #[Route("/api/profiles/listing/city/{city}/county/{county}", name: "api.profile_listing.by_county", methods: "GET", format: "json")]
  313.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  314.     #[OA\Tag(name: "ProfileListing")]
  315.     #[OA\Response(
  316.         response: 200,
  317.         description: '',
  318.         content: new OA\JsonContent(ref: "#/components/schemas/ProfileListingPage")
  319.     )]
  320.     public function listByCounty(Request $request, City $city, County $county): Response
  321.     {
  322.         if (!$city->hasCounty($county)) {
  323.             throw $this->createNotFoundException();
  324.         }
  325.         $specs = $this->profileListSpecificationService->listByCounty($county);
  326.         $response = new Response();
  327.         $alternativeSpec = Spec::orX(ProfileIsLocated::withinCounties($city, $city->getCounties()->toArray()));
  328.         $result = $this->paginatedListing($city, '/city/{city}/county/{county}', ['city' => $city->getId(), 'county' => $county->getId()], $specs->spec(), $alternativeSpec, self::RESULT_SOURCE_COUNTY, $response);
  329.         if ($this->isApiRequest($request)) {
  330.             return $this->jsonListingResponse($result, $city, $specs);
  331.         }
  332.         return $this->render('ProfileList/list.html.twig', [
  333.             'profiles' => $result,
  334.             'source' => $this->source,
  335.             'source_default' => self::RESULT_SOURCE_COUNTY,
  336.             'county' => $county,
  337.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  338.                 'city' => $city->getUriIdentity(),
  339.                 'county' => $county->getUriIdentity(),
  340.                 'page' => $this->getCurrentPageNumber()
  341.             ]),
  342.             'recommendationSpec' => $specs->recommendationSpec(),
  343.         ], response: $response);
  344.     }
  345.     #[ParamConverter("city", converter: "city_converter")]
  346.     #[Entity("district", expr: "repository.ofUriIdentityWithinCity(district, city)")]
  347.     #[Route("/api/profiles/listing/city/{city}/district/{district}", name: "api.profile_listing.by_district", methods: "GET", format: "json")]
  348.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  349.     #[OA\Tag(name: "ProfileListing")]
  350.     #[OA\Response(
  351.         response: 200,
  352.         description: '',
  353.         content: new OA\JsonContent(ref: "#/components/schemas/ProfileListingPage")
  354.     )]
  355.     public function listByDistrict(Request $request, City $city, District $district): Response
  356.     {
  357.         if (!$city->hasDistrict($district)) {
  358.             throw $this->createNotFoundException();
  359.         }
  360.         $specs = $this->profileListSpecificationService->listByDistrict($district);
  361.         $response = new Response();
  362.         $alternativeSpec = Spec::orX(ProfileIsLocated::withinDistricts($city, $city->getDistricts()->toArray()));
  363.         $result = $this->paginatedListing($city, '/city/{city}/district/{district}', ['city' => $city->getId(), 'district' => $district->getId()], $specs->spec(), $alternativeSpec, self::RESULT_SOURCE_DISTRICT, $response);
  364.         if ($this->isApiRequest($request)) {
  365.             return $this->jsonListingResponse($result, $city, $specs);
  366.         }
  367.         return $this->render('ProfileList/list.html.twig', [
  368.             'profiles' => $result,
  369.             'source' => $this->source,
  370.             'source_default' => self::RESULT_SOURCE_DISTRICT,
  371.             'district' => $district,
  372.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  373.                 'city' => $city->getUriIdentity(),
  374.                 'district' => $district->getUriIdentity(),
  375.                 'page' => $this->getCurrentPageNumber()
  376.             ]),
  377.             'recommendationSpec' => $specs->recommendationSpec(),
  378.         ], response: $response);
  379.     }
  380.     /**
  381.      * @Feature("extra_category_without_prepayment")
  382.      */
  383.     #[ParamConverter("city", converter: "city_converter")]
  384.     #[Route("/api/profiles/listing/city/{city}/category/without_prepayment", name: "api.profile_listing.category.without_prepayment", methods: "GET", format: "json")]
  385.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  386.     #[OA\Tag(name: "ProfileListing")]
  387.     #[OA\Response(
  388.         response: 200,
  389.         description: '',
  390.         content: new OA\JsonContent(ref: "#/components/schemas/ProfileListingPage")
  391.     )]
  392.     public function listWithoutPrepayment(Request $request, City $city): Response
  393.     {
  394.         $listingData = $this->profileListingDataCreator->getListingDataForFilter('listWithoutPrepayment', [], $city);
  395.         $specs = $listingData['specs'];
  396.         $listingTypeName = $listingData['listingTypeName'];
  397.         $response = new Response();
  398.         $result = $this->paginatedListing($city, '/city/{city}/category/without_prepayment', ['city' => $city->getId()], $specs->spec(), null, null, $response);
  399.         if ($this->isApiRequest($request)) {
  400.             return $this->jsonListingResponse($result, $city, $specs);
  401.         }
  402.         $request->attributes->set('profiles_count', $result->totalCount());
  403.         $request->attributes->set('listingTypeName', $listingTypeName);
  404.         $request->attributes->set('city', $city);
  405.         return $this->render('ProfileList/list.html.twig', [
  406.             'profiles' => $result,
  407.             'source' => $this->source,
  408.             'source_default' => 'without_prepayment',
  409.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  410.                 'city' => $city->getUriIdentity(),
  411.                 'page' => $this->getCurrentPageNumber(),
  412.             ]),
  413.             'recommendationSpec' => $specs->recommendationSpec(),
  414.         ], response: $response);
  415.     }
  416.     #[ParamConverter("city", converter: "city_converter")]
  417.     #[Entity("station", expr: "repository.ofUriIdentityWithinCity(station, city)")]
  418.     #[Route("/api/profiles/listing/city/{city}/station/{station}", name: "api.profile_listing.by_station", methods: "GET", format: "json")]
  419.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  420.     #[OA\Tag(name: "ProfileListing")]
  421.     #[OA\Response(
  422.         response: 200,
  423.         description: '',
  424.         content: new OA\JsonContent(ref: "#/components/schemas/ProfileListingPage")
  425.     )]
  426.     public function listByStation(Request $request, City $city, Station $station): Response
  427.     {
  428.         if (!$city->hasStation($station)) {
  429.             throw $this->createNotFoundException();
  430.         }
  431.         $specs = $this->profileListSpecificationService->listByStation($station);
  432.         $response = new Response();
  433.         $result = $this->paginatedListing($city, '/city/{city}/station/{station}', ['city' => $city->getId(), 'station' => $station->getId()], $specs->spec(), null, null, $response);
  434.         if ($this->isApiRequest($request)) {
  435.             return $this->jsonListingResponse($result, $city, $specs);
  436.         }
  437.         return $this->render('ProfileList/list.html.twig', [
  438.             'profiles' => $result,
  439.             'source' => $this->source,
  440.             'source_default' => self::RESULT_SOURCE_STATION,
  441.             'station' => $station,
  442.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  443.                 'city' => $city->getUriIdentity(),
  444.                 'station' => $station->getUriIdentity(),
  445.                 'page' => $this->getCurrentPageNumber()
  446.             ]),
  447.             'recommendationSpec' => $specs->recommendationSpec(),
  448.         ], response: $response);
  449.     }
  450.     #[ParamConverter("city", converter: "city_converter")]
  451.     public function listByStations(City $city, string $stations, StationRepository $stationRepository): Response
  452.     {
  453.         $stationIds = explode(',', $stations);
  454.         $stations = $stationRepository->findBy(['uriIdentity' => $stationIds]);
  455.         $specs = $this->profileListSpecificationService->listByStations($stations);
  456.         $response = new Response();
  457.         $result = $this->paginatedListing($city, null, [], $specs->spec(), null, null, $response);
  458.         return $this->render('ProfileList/list.html.twig', [
  459.             'profiles' => $result,
  460.             'recommendationSpec' => $specs->recommendationSpec(),
  461.         ]);
  462.     }
  463.     #[ParamConverter("city", converter: "city_converter")]
  464.     #[Route("/api/profiles/listing/city/{city}/approved", name: "api.profile_listing.approved", methods: "GET", format: "json")]
  465.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  466.     #[OA\Tag(name: "ProfileListing")]
  467.     #[OA\Response(
  468.         response: 200,
  469.         description: '',
  470.         content: new OA\JsonContent(ref: "#/components/schemas/ProfileListingPage")
  471.     )]
  472.     public function listApproved(Request $request, City $city): Response
  473.     {
  474.         $specs = $this->profileListSpecificationService->listApproved();
  475.         $response = new Response();
  476.         $result = $this->paginatedListing($city, '/city/{city}/approved', ['city' => $city->getId()], $specs->spec(), null, null, $response);
  477.         if ($this->isApiRequest($request)) {
  478.             return $this->jsonListingResponse($result, $city, $specs);
  479.         }
  480.         return $this->render('ProfileList/list.html.twig', [
  481.             'profiles' => $result,
  482.             'source' => $this->source,
  483.             'source_default' => self::RESULT_SOURCE_APPROVED,
  484.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  485.                 'city' => $city->getUriIdentity(),
  486.                 'page' => $this->getCurrentPageNumber()
  487.             ]),
  488.             'recommendationSpec' => $specs->recommendationSpec(),
  489.         ], response: $response);
  490.     }
  491.     /**
  492.      * @Feature("extra_category_with_whatsapp")
  493.      */
  494.     #[ParamConverter("city", converter: "city_converter")]
  495.     #[Route("/api/profiles/listing/city/{city}/category/whatsapp", name: "api.profile_listing.category.whatsapp", methods: "GET", format: "json")]
  496.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  497.     #[OA\Tag(name: "ProfileListing")]
  498.     #[OA\Response(
  499.         response: 200,
  500.         description: '',
  501.         content: new OA\JsonContent(ref: "#/components/schemas/ProfileListingPage")
  502.     )]
  503.     public function listWithWhatsapp(Request $request, City $city): Response
  504.     {
  505.         $specs = $this->profileListSpecificationService->listWithWhatsapp();
  506.         $response = new Response();
  507.         $result = $this->paginatedListing($city, '/city/{city}/category/whatsapp', ['city' => $city->getId()], $specs->spec(), null, null, $response);
  508.         if ($this->isApiRequest($request)) {
  509.             return $this->jsonListingResponse($result, $city, $specs);
  510.         }
  511.         return $this->render('ProfileList/list.html.twig', [
  512.             'profiles' => $result,
  513.             'source' => $this->source,
  514.             'source_default' => self::RESULT_SOURCE_WITH_WHATSAPP,
  515.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  516.                 'city' => $city->getUriIdentity(),
  517.                 'page' => $this->getCurrentPageNumber(),
  518.             ]),
  519.             'recommendationSpec' => $specs->recommendationSpec(),
  520.         ], response: $response);
  521.     }
  522.     /**
  523.      * @Feature("extra_category_with_telegram")
  524.      */
  525.     #[ParamConverter("city", converter: "city_converter")]
  526.     #[Route("/api/profiles/listing/city/{city}/category/telegram", name: "api.profile_listing.category.telegram", methods: "GET", format: "json")]
  527.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  528.     #[OA\Tag(name: "ProfileListing")]
  529.     #[OA\Response(
  530.         response: 200,
  531.         description: '',
  532.         content: new OA\JsonContent(ref: "#/components/schemas/ProfileListingPage")
  533.     )]
  534.     public function listWithTelegram(Request $request, City $city): Response
  535.     {
  536.         $specs = $this->profileListSpecificationService->listWithTelegram();
  537.         $response = new Response();
  538.         $result = $this->paginatedListing($city, '/city/{city}/category/telegram', ['city' => $city->getId()], $specs->spec(), null, null, $response);
  539.         if ($this->isApiRequest($request)) {
  540.             return $this->jsonListingResponse($result, $city, $specs);
  541.         }
  542.         return $this->render('ProfileList/list.html.twig', [
  543.             'profiles' => $result,
  544.             'source' => $this->source,
  545.             'source_default' => self::RESULT_SOURCE_WITH_TELEGRAM,
  546.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  547.                 'city' => $city->getUriIdentity(),
  548.                 'page' => $this->getCurrentPageNumber(),
  549.             ]),
  550.             'recommendationSpec' => $specs->recommendationSpec(),
  551.         ], response: $response);
  552.     }
  553.     /**
  554.      * @Feature("extra_category_eighteen_years_old")
  555.      */
  556.     #[ParamConverter("city", converter: "city_converter")]
  557.     #[Route("/api/profiles/listing/city/{city}/category/eighteen_years_old", name: "api.profile_listing.category.eighteen_years_old", methods: "GET", format: "json")]
  558.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  559.     #[OA\Tag(name: "ProfileListing")]
  560.     #[OA\Response(
  561.         response: 200,
  562.         description: '',
  563.         content: new OA\JsonContent(ref: "#/components/schemas/ProfileListingPage")
  564.     )]
  565.     public function listEighteenYearsOld(Request $request, City $city): Response
  566.     {
  567.         $specs = $this->profileListSpecificationService->listEighteenYearsOld();
  568.         $response = new Response();
  569.         $result = $this->paginatedListing($city, '/city/{city}/category/eighteen_years_old', ['city' => $city->getId()], $specs->spec(), null, null, $response);
  570.         if ($this->isApiRequest($request)) {
  571.             return $this->jsonListingResponse($result, $city, $specs);
  572.         }
  573.         return $this->render('ProfileList/list.html.twig', [
  574.             'profiles' => $result,
  575.             'source' => $this->source,
  576.             'source_default' => self::RESULT_SOURCE_EIGHTEEN_YEARS_OLD,
  577.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  578.                 'city' => $city->getUriIdentity(),
  579.                 'page' => $this->getCurrentPageNumber(),
  580.             ]),
  581.             'recommendationSpec' => $specs->recommendationSpec(),
  582.         ], response: $response);
  583.     }
  584.     /**
  585.      * @Feature("extra_category_rublevskie")
  586.      */
  587.     #[ParamConverter("city", converter: "city_converter")]
  588.     #[Route("/api/profiles/listing/city/{city}/category/rublevskie", name: "api.profile_listing.category.rublevskie", methods: "GET", format: "json")]
  589.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  590.     #[OA\Tag(name: "ProfileListing")]
  591.     #[OA\Response(
  592.         response: 200,
  593.         description: '',
  594.         content: new OA\JsonContent(ref: "#/components/schemas/ProfileListingPage")
  595.     )]
  596.     public function listRublevskie(Request $request, City $city): Response
  597.     {
  598.         if ($city->getUriIdentity() !== 'moscow') {
  599.             throw $this->createNotFoundException();
  600.         }
  601.         $specs = $this->profileListSpecificationService->listRublevskie($city);
  602.         $response = new Response();
  603.         $result = $this->paginatedListing($city, '/city/{city}/category/rublevskie', ['city' => $city->getId()], $specs->spec(), null, null, $response);
  604.         if ($this->isApiRequest($request)) {
  605.             return $this->jsonListingResponse($result, $city, $specs);
  606.         }
  607.         return $this->render('ProfileList/list.html.twig', [
  608.             'profiles' => $result,
  609.             'source' => $this->source,
  610.             'source_default' => 'rublevskie',
  611.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  612.                 'city' => $city->getUriIdentity(),
  613.                 'page' => $this->getCurrentPageNumber(),
  614.             ]),
  615.             'recommendationSpec' => $specs->recommendationSpec(),
  616.         ], response: $response);
  617.     }
  618.     /**
  619.      * @Feature("extra_category_with_tattoo")
  620.      */
  621.     #[ParamConverter("city", converter: "city_converter")]
  622.     #[Route("/api/profiles/listing/city/{city}/category/with_tattoo", name: "api.profile_listing.category.with_tattoo", methods: "GET", format: "json")]
  623.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  624.     #[OA\Tag(name: "ProfileListing")]
  625.     #[OA\Response(
  626.         response: 200,
  627.         description: '',
  628.         content: new OA\JsonContent(ref: "#/components/schemas/ProfileListingPage")
  629.     )]
  630.     public function listWithTattoo(Request $request, City $city): Response
  631.     {
  632.         $specs = $this->profileListSpecificationService->listWithTattoo();
  633.         $response = new Response();
  634.         $result = $this->paginatedListing($city, '/city/{city}/category/with_tattoo', ['city' => $city->getId()], $specs->spec(), null, null, $response);
  635.         if ($this->isApiRequest($request)) {
  636.             return $this->jsonListingResponse($result, $city, $specs);
  637.         }
  638.         return $this->render('ProfileList/list.html.twig', [
  639.             'profiles' => $result,
  640.             'source' => $this->source,
  641.             'source_default' => self::RESULT_SOURCE_WITH_TATTOO,
  642.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  643.                 'city' => $city->getUriIdentity(),
  644.                 'page' => $this->getCurrentPageNumber(),
  645.             ]),
  646.             'recommendationSpec' => $specs->recommendationSpec(),
  647.         ], response: $response);
  648.     }
  649.     #[ParamConverter("city", converter: "city_converter")]
  650.     #[Route("/api/profiles/listing/city/{city}/with_comments", name: "api.profile_listing.with_comments", methods: "GET", format: "json")]
  651.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  652.     #[OA\Tag(name: "ProfileListing")]
  653.     #[OA\Response(
  654.         response: 200,
  655.         description: '',
  656.         content: new OA\JsonContent(ref: "#/components/schemas/ProfileListingPage")
  657.     )]
  658.     public function listWithComments(Request $request, City $city): Response
  659.     {
  660.         $specs = $this->profileListSpecificationService->listWithComments();
  661.         $response = new Response();
  662.         $result = $this->paginatedListing($city, '/city/{city}/with_comments', ['city' => $city->getId()], $specs->spec(), null, null, $response);
  663.         if ($this->isApiRequest($request)) {
  664.             return $this->jsonListingResponse($result, $city, $specs);
  665.         }
  666.         return $this->render('ProfileList/list.html.twig', [
  667.             'profiles' => $result,
  668.             'source' => $this->source,
  669.             'source_default' => self::RESULT_SOURCE_WITH_COMMENTS,
  670.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  671.                 'city' => $city->getUriIdentity(),
  672.                 'page' => $this->getCurrentPageNumber()
  673.             ]),
  674.             'recommendationSpec' => $specs->recommendationSpec(),
  675.         ], response: $response);
  676.     }
  677.     /**
  678.      * @Feature("extra_category_with_piercing")
  679.      */
  680.     #[ParamConverter("city", converter: "city_converter")]
  681.     #[Route("/api/profiles/listing/city/{city}/category/with_piercing", name: "api.profile_listing.category.with_piercing", methods: "GET", format: "json")]
  682.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  683.     #[OA\Tag(name: "ProfileListing")]
  684.     #[OA\Response(
  685.         response: 200,
  686.         description: '',
  687.         content: new OA\JsonContent(ref: "#/components/schemas/ProfileListingPage")
  688.     )]
  689.     public function listWithPiercing(Request $request, City $city): Response
  690.     {
  691.         $specs = $this->profileListSpecificationService->listWithPiercing();
  692.         $response = new Response();
  693.         $result = $this->paginatedListing($city, '/city/{city}/category/with_piercing', ['city' => $city->getId()], $specs->spec(), null, null, $response);
  694.         if ($this->isApiRequest($request)) {
  695.             return $this->jsonListingResponse($result, $city, $specs);
  696.         }
  697.         return $this->render('ProfileList/list.html.twig', [
  698.             'profiles' => $result,
  699.             'source' => $this->source,
  700.             'source_default' => self::RESULT_SOURCE_WITH_PIERCING,
  701.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  702.                 'city' => $city->getUriIdentity(),
  703.                 'page' => $this->getCurrentPageNumber(),
  704.             ]),
  705.             'recommendationSpec' => $specs->recommendationSpec(),
  706.         ], response: $response);
  707.     }
  708.     #[ParamConverter("city", converter: "city_converter")]
  709.     #[Route("/api/profiles/listing/city/{city}/with_video", name: "api.profile_listing.with_video", methods: "GET", format: "json")]
  710.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  711.     #[OA\Tag(name: "ProfileListing")]
  712.     #[OA\Response(
  713.         response: 200,
  714.         description: '',
  715.         content: new OA\JsonContent(ref: "#/components/schemas/ProfileListingPage")
  716.     )]
  717.     public function listWithVideo(Request $request, City $city): Response
  718.     {
  719.         $specs = $this->profileListSpecificationService->listWithVideo();
  720.         $response = new Response();
  721.         $result = $this->paginatedListing($city, '/city/{city}/with_video', ['city' => $city->getId()], $specs->spec(), null, null, $response);
  722.         if ($this->isApiRequest($request)) {
  723.             return $this->jsonListingResponse($result, $city, $specs);
  724.         }
  725.         return $this->render('ProfileList/list.html.twig', [
  726.             'profiles' => $result,
  727.             'source' => $this->source,
  728.             'source_default' => self::RESULT_SOURCE_WITH_VIDEO,
  729.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  730.                 'city' => $city->getUriIdentity(),
  731.                 'page' => $this->getCurrentPageNumber()
  732.             ]),
  733.             'recommendationSpec' => $specs->recommendationSpec(),
  734.         ], response: $response);
  735.     }
  736.      /**
  737.      * @Feature("extra_category_round_the_clock")
  738.      */
  739.     #[ParamConverter("city", converter: "city_converter")]
  740.     #[Route("/api/profiles/listing/city/{city}/category/round_the_clock", name: "api.profile_listing.category.round_the_clock", methods: "GET", format: "json")]
  741.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  742.     #[OA\Tag(name: "ProfileListing")]
  743.     #[OA\Response(
  744.         response: 200,
  745.         description: '',
  746.         content: new OA\JsonContent(ref: "#/components/schemas/ProfileListingPage")
  747.     )]
  748.     public function listRoundTheClock(Request $request, City $city): Response
  749.     {
  750.         $specs = $this->profileListSpecificationService->listRoundTheClock();
  751.         $response = new Response();
  752.         $result = $this->paginatedListing($city, '/city/{city}/category/round_the_clock', ['city' => $city->getId()], $specs->spec(), null, null, $response);
  753.         if ($this->isApiRequest($request)) {
  754.             return $this->jsonListingResponse($result, $city, $specs);
  755.         }
  756.         return $this->render('ProfileList/list.html.twig', [
  757.             'profiles' => $result,
  758.             'source' => $this->source,
  759.             'source_default' => self::RESULT_SOURCE_ROUND_THE_CLOCK,
  760.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  761.                 'city' => $city->getUriIdentity(),
  762.                 'page' => $this->getCurrentPageNumber(),
  763.             ]),
  764.             'recommendationSpec' => $specs->recommendationSpec(),
  765.         ], response: $response);
  766.     }
  767.     /**
  768.      * @Feature("extra_category_for_two_hours")
  769.      */
  770.     #[ParamConverter("city", converter: "city_converter")]
  771.     #[Route("/api/profiles/listing/city/{city}/category/for_two_hours", name: "api.profile_listing.category.for_two_hours", methods: "GET", format: "json")]
  772.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  773.     #[OA\Tag(name: "ProfileListing")]
  774.     #[OA\Response(
  775.         response: 200,
  776.         description: '',
  777.         content: new OA\JsonContent(ref: "#/components/schemas/ProfileListingPage")
  778.     )]
  779.     public function listForTwoHours(Request $request, City $city): Response
  780.     {
  781.         $specs = $this->profileListSpecificationService->listForTwoHours();
  782.         $response = new Response();
  783.         $result = $this->paginatedListing($city, '/city/{city}/category/for_two_hours', ['city' => $city->getId()], $specs->spec(), null, null, $response);
  784.         if ($this->isApiRequest($request)) {
  785.             return $this->jsonListingResponse($result, $city, $specs);
  786.         }
  787.         return $this->render('ProfileList/list.html.twig', [
  788.             'profiles' => $result,
  789.             'source' => $this->source,
  790.             'source_default' => self::RESULT_SOURCE_FOR_TWO_HOURS,
  791.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  792.                 'city' => $city->getUriIdentity(),
  793.                 'page' => $this->getCurrentPageNumber(),
  794.             ]),
  795.             'recommendationSpec' => $specs->recommendationSpec(),
  796.         ], response: $response);
  797.     }
  798.     /**
  799.      * @Feature("extra_category_for_hour")
  800.      */
  801.     #[ParamConverter("city", converter: "city_converter")]
  802.     #[Route("/api/profiles/listing/city/{city}/category/for_hour", name: "api.profile_listing.category.for_hour", methods: "GET", format: "json")]
  803.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  804.     #[OA\Tag(name: "ProfileListing")]
  805.     #[OA\Response(
  806.         response: 200,
  807.         description: '',
  808.         content: new OA\JsonContent(ref: "#/components/schemas/ProfileListingPage")
  809.     )]
  810.     public function listForHour(Request $request, City $city): Response
  811.     {
  812.         $specs = $this->profileListSpecificationService->listForHour();
  813.         $response = new Response();
  814.         $result = $this->paginatedListing($city, '/city/{city}/category/for_hour', ['city' => $city->getId()], $specs->spec(), null, null, $response);
  815.         if ($this->isApiRequest($request)) {
  816.             return $this->jsonListingResponse($result, $city, $specs);
  817.         }
  818.         return $this->render('ProfileList/list.html.twig', [
  819.             'profiles' => $result,
  820.             'source' => $this->source,
  821.             'source_default' => self::RESULT_SOURCE_FOR_HOUR,
  822.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  823.                 'city' => $city->getUriIdentity(),
  824.                 'page' => $this->getCurrentPageNumber(),
  825.             ]),
  826.             'recommendationSpec' => $specs->recommendationSpec(),
  827.         ], response: $response);
  828.     }
  829.     /**
  830.      * @Feature("extra_category_express_program")
  831.      */
  832.     #[ParamConverter("city", converter: "city_converter")]
  833.     #[Route("/api/profiles/listing/city/{city}/category/express", name: "api.profile_listing.category.express", methods: "GET", format: "json")]
  834.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  835.     #[OA\Tag(name: "ProfileListing")]
  836.     #[OA\Response(
  837.         response: 200,
  838.         description: '',
  839.         content: new OA\JsonContent(ref: "#/components/schemas/ProfileListingPage")
  840.     )]
  841.     public function listExpress(Request $request, City $city): Response
  842.     {
  843.         $specs = $this->profileListSpecificationService->listExpress();
  844.         $response = new Response();
  845.         $result = $this->paginatedListing($city, '/city/{city}/category/express', ['city' => $city->getId()], $specs->spec(), null, null, $response);
  846.         if ($this->isApiRequest($request)) {
  847.             return $this->jsonListingResponse($result, $city, $specs);
  848.         }
  849.         return $this->render('ProfileList/list.html.twig', [
  850.             'profiles' => $result,
  851.             'source' => $this->source,
  852.             'source_default' => self::RESULT_SOURCE_EXPRESS_PROGRAM,
  853.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  854.                 'city' => $city->getUriIdentity(),
  855.                 'page' => $this->getCurrentPageNumber(),
  856.             ]),
  857.             'recommendationSpec' => $specs->recommendationSpec(),
  858.         ], response: $response);
  859.     }
  860.     /**
  861.      * @Feature("extra_category_grandmothers")
  862.      */
  863.     #[ParamConverter("city", converter: "city_converter")]
  864.     #[Route("/api/profiles/listing/city/{city}/category/grandmothers", name: "api.profile_listing.category.grandmothers", methods: "GET", format: "json")]
  865.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  866.     #[OA\Tag(name: "ProfileListing")]
  867.     #[OA\Response(
  868.         response: 200,
  869.         description: '',
  870.         content: new OA\JsonContent(ref: "#/components/schemas/ProfileListingPage")
  871.     )]
  872.     public function listGrandmothers(Request $request, City $city): Response
  873.     {
  874.         $specs = $this->profileListSpecificationService->listGrandmothers();
  875.         $response = new Response();
  876.         $result = $this->paginatedListing($city, '/city/{city}/category/grandmothers', ['city' => $city->getId()], $specs->spec(), null, null, $response);
  877.         if ($this->isApiRequest($request)) {
  878.             return $this->jsonListingResponse($result, $city, $specs);
  879.         }
  880.         return $this->render('ProfileList/list.html.twig', [
  881.             'profiles' => $result,
  882.             'source' => $this->source,
  883.             'source_default' => 'grandmothers',
  884.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  885.                 'city' => $city->getUriIdentity(),
  886.                 'page' => $this->getCurrentPageNumber(),
  887.             ]),
  888.             'recommendationSpec' => $specs->recommendationSpec(),
  889.         ], response: $response);
  890.     }
  891.         /**
  892.      * @Feature("extra_category_big_breast")
  893.      */
  894.     #[ParamConverter("city", converter: "city_converter")]
  895.     #[Route("/api/profiles/listing/city/{city}/category/big_breast", name: "api.profile_listing.category.big_breast", methods: "GET", format: "json")]
  896.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  897.     #[OA\Tag(name: "ProfileListing")]
  898.     #[OA\Response(
  899.         response: 200,
  900.         description: '',
  901.         content: new OA\JsonContent(ref: "#/components/schemas/ProfileListingPage")
  902.     )]
  903.     public function listBigBreast(Request $request, City $city): Response
  904.     {
  905.         $specs = $this->profileListSpecificationService->listBigBreast();
  906.         $response = new Response();
  907.         $result = $this->paginatedListing($city, '/city/{city}/category/big_breast', ['city' => $city->getId()], $specs->spec(), null, null, $response);
  908.         if ($this->isApiRequest($request)) {
  909.             return $this->jsonListingResponse($result, $city, $specs);
  910.         }
  911.         return $this->render('ProfileList/list.html.twig', [
  912.             'profiles' => $result,
  913.             'source' => $this->source,
  914.             'source_default' => 'big_breast',
  915.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  916.                 'city' => $city->getUriIdentity(),
  917.                 'page' => $this->getCurrentPageNumber(),
  918.             ]),
  919.             'recommendationSpec' => $specs->recommendationSpec(),
  920.         ], response: $response);
  921.     }
  922.     /**
  923.      * @Feature("extra_category_very_skinny")
  924.      */
  925.     #[ParamConverter("city", converter: "city_converter")]
  926.     #[Route("/api/profiles/listing/city/{city}/category/very_skinny", name: "api.profile_listing.category.very_skinny", methods: "GET", format: "json")]
  927.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  928.     #[OA\Tag(name: "ProfileListing")]
  929.     #[OA\Response(
  930.         response: 200,
  931.         description: '',
  932.         content: new OA\JsonContent(ref: "#/components/schemas/ProfileListingPage")
  933.     )]
  934.     public function listVerySkinny(Request $request, City $city): Response
  935.     {
  936.         $specs = $this->profileListSpecificationService->listVerySkinny();
  937.         $response = new Response();
  938.         $result = $this->paginatedListing($city, '/city/{city}/category/very_skinny', ['city' => $city->getId()], $specs->spec(), null, null, $response);
  939.         if ($this->isApiRequest($request)) {
  940.             return $this->jsonListingResponse($result, $city, $specs);
  941.         }
  942.         return $this->render('ProfileList/list.html.twig', [
  943.             'profiles' => $result,
  944.             'source' => $this->source,
  945.             'source_default' => 'very_skinny',
  946.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  947.                 'city' => $city->getUriIdentity(),
  948.                 'page' => $this->getCurrentPageNumber(),
  949.             ]),
  950.             'recommendationSpec' => $specs->recommendationSpec(),
  951.         ], response: $response);
  952.     }
  953.     /**
  954.      * @Feature("extra_category_small_ass")
  955.      */
  956.     #[ParamConverter("city", converter: "city_converter")]
  957.     #[Route("/api/profiles/listing/city/{city}/category/small_ass", name: "api.profile_listing.category.small_ass", methods: "GET", format: "json")]
  958.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  959.     #[OA\Tag(name: "ProfileListing")]
  960.     #[OA\Response(
  961.         response: 200,
  962.         description: '',
  963.         content: new OA\JsonContent(ref: "#/components/schemas/ProfileListingPage")
  964.     )]
  965.     public function listSmallAss(Request $request, City $city): Response
  966.     {
  967.         $specs = $this->profileListSpecificationService->listSmallAss();
  968.         $response = new Response();
  969.         $result = $this->paginatedListing($city, '/city/{city}/category/small_ass', ['city' => $city->getId()], $specs->spec(), null, null, $response);
  970.         if ($this->isApiRequest($request)) {
  971.             return $this->jsonListingResponse($result, $city, $specs);
  972.         }
  973.         return $this->render('ProfileList/list.html.twig', [
  974.             'profiles' => $result,
  975.             'source' => $this->source,
  976.             'source_default' => 'small_ass',
  977.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  978.                 'city' => $city->getUriIdentity(),
  979.                 'page' => $this->getCurrentPageNumber(),
  980.             ]),
  981.             'recommendationSpec' => $specs->recommendationSpec(),
  982.         ], response: $response);
  983.     }
  984.     /**
  985.      * @Feature("extra_category_beautiful_prostitutes")
  986.      */
  987.     #[ParamConverter("city", converter: "city_converter")]
  988.     #[Route("/api/profiles/listing/city/{city}/category/beautiful_prostitutes", name: "api.profile_listing.category.beautiful_prostitutes", methods: "GET", format: "json")]
  989.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  990.     #[OA\Tag(name: "ProfileListing")]
  991.     #[OA\Response(
  992.         response: 200,
  993.         description: '',
  994.         content: new OA\JsonContent(ref: "#/components/schemas/ProfileListingPage")
  995.     )]
  996.     public function listBeautifulProstitutes(Request $request, City $city): Response
  997.     {
  998.         $specs = $this->profileListSpecificationService->listBeautifulProstitutes();
  999.         $response = new Response();
  1000.         $result = $this->paginatedListing($city, '/city/{city}/category/beautiful_prostitutes', ['city' => $city->getId()], $specs->spec(), null, null, $response);
  1001.         if ($this->isApiRequest($request)) {
  1002.             return $this->jsonListingResponse($result, $city, $specs);
  1003.         }
  1004.         return $this->render('ProfileList/list.html.twig', [
  1005.             'profiles' => $result,
  1006.             'source' => $this->source,
  1007.             'source_default' => 'beautiful_prostitutes',
  1008.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  1009.                 'city' => $city->getUriIdentity(),
  1010.                 'page' => $this->getCurrentPageNumber(),
  1011.             ]),
  1012.             'recommendationSpec' => $specs->recommendationSpec(),
  1013.         ], response: $response);
  1014.     }
  1015.     /**
  1016.      * @Feature("extra_category_without_intermediaries")
  1017.      */
  1018.     #[ParamConverter("city", converter: "city_converter")]
  1019.     #[Route("/api/profiles/listing/city/{city}/category/without_intermediaries", name: "api.profile_listing.category.without_intermediaries", methods: "GET", format: "json")]
  1020.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  1021.     #[OA\Tag(name: "ProfileListing")]
  1022.     #[OA\Response(
  1023.         response: 200,
  1024.         description: '',
  1025.         content: new OA\JsonContent(ref: "#/components/schemas/ProfileListingPage")
  1026.     )]
  1027.     public function listWithoutIntermediaries(Request $request, City $city): Response
  1028.     {
  1029.         $specs = $this->profileListSpecificationService->listWithoutIntermediaries();
  1030.         $response = new Response();
  1031.         $result = $this->paginatedListing($city, '/city/{city}/category/without_intermediaries', ['city' => $city->getId()], $specs->spec(), null, null, $response);
  1032.         if ($this->isApiRequest($request)) {
  1033.             return $this->jsonListingResponse($result, $city, $specs);
  1034.         }
  1035.         return $this->render('ProfileList/list.html.twig', [
  1036.             'profiles' => $result,
  1037.             'source' => $this->source,
  1038.             'source_default' => 'without_intermediaries',
  1039.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  1040.                 'city' => $city->getUriIdentity(),
  1041.                 'page' => $this->getCurrentPageNumber(),
  1042.             ]),
  1043.             'recommendationSpec' => $specs->recommendationSpec(),
  1044.         ], response: $response);
  1045.     }
  1046.     /**
  1047.      * @Feature("extra_category_intim_services")
  1048.      */
  1049.     #[ParamConverter("city", converter: "city_converter")]
  1050.     public function intimServices(Request $request, City $city): Response
  1051.     {
  1052.         $servicesByGroup = $this->serviceRepository->allIndexedByGroup();
  1053.         return $this->render('ProfileList/intim_services.html.twig', [
  1054.             'city' => $city,
  1055.             'servicesByGroup' => $servicesByGroup,
  1056.             'skipSetCurrentListingPage' => true,
  1057.         ]);
  1058.     }
  1059.     /**
  1060.      * @Feature("extra_category_outcall")
  1061.      */
  1062.     #[ParamConverter("city", converter: "city_converter")]
  1063.     #[Route("/api/profiles/listing/city/{city}/category/outcall", name: "api.profile_listing.category.outcall", methods: "GET", format: "json")]
  1064.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  1065.     #[OA\Tag(name: "ProfileListing")]
  1066.     #[OA\Response(
  1067.         response: 200,
  1068.         description: '',
  1069.         content: new OA\JsonContent(ref: "#/components/schemas/ProfileListingPage")
  1070.     )]
  1071.     public function listOutcall(Request $request, City $city): Response
  1072.     {
  1073.         $specs = $this->profileListSpecificationService->listByOutcall();
  1074.         $response = new Response();
  1075.         $result = $this->paginatedListing($city, '/city/{city}/category/outcall', ['city' => $city->getId()], $specs->spec(), null, null, $response);
  1076.         if ($this->isApiRequest($request)) {
  1077.             return $this->jsonListingResponse($result, $city, $specs);
  1078.         }
  1079.         return $this->render('ProfileList/list.html.twig', [
  1080.             'profiles' => $result,
  1081.             'source' => $this->source,
  1082.             'source_default' => 'outcall',
  1083.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  1084.                 'city' => $city->getUriIdentity(),
  1085.                 'page' => $this->getCurrentPageNumber(),
  1086.             ]),
  1087.             'recommendationSpec' => $specs->recommendationSpec(),
  1088.         ], response: $response);
  1089.     }
  1090.     /**
  1091.      * @Feature("extra_category_dwarfs")
  1092.      */
  1093.     #[ParamConverter("city", converter: "city_converter")]
  1094.     #[Route("/api/profiles/listing/city/{city}/category/dwarfs", name: "api.profile_listing.category.dwarfs", methods: "GET", format: "json")]
  1095.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  1096.     #[OA\Tag(name: "ProfileListing")]
  1097.     #[OA\Response(
  1098.         response: 200,
  1099.         description: '',
  1100.         content: new OA\JsonContent(ref: "#/components/schemas/ProfileListingPage")
  1101.     )]
  1102.     public function listDwarfs(Request $request, City $city): Response
  1103.     {
  1104.         $specs = $this->profileListSpecificationService->listDwarfs();
  1105.         $response = new Response();
  1106.         $result = $this->paginatedListing($city, '/city/{city}/category/dwarfs', ['city' => $city->getId()], $specs->spec(), null, null, $response);
  1107.         if ($this->isApiRequest($request)) {
  1108.             return $this->jsonListingResponse($result, $city, $specs);
  1109.         }
  1110.         return $this->render('ProfileList/list.html.twig', [
  1111.             'profiles' => $result,
  1112.             'source' => $this->source,
  1113.             'source_default' => 'dwarfs',
  1114.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  1115.                 'city' => $city->getUriIdentity(),
  1116.                 'page' => $this->getCurrentPageNumber(),
  1117.             ]),
  1118.             'recommendationSpec' => $specs->recommendationSpec(),
  1119.         ], response: $response);
  1120.     }
  1121.     #[ParamConverter("city", converter: "city_converter")]
  1122.     #[Route("/api/profiles/listing/city/{city}/with_selfie", name: "api.profile_listing.with_selfie", methods: "GET", format: "json")]
  1123.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  1124.     #[OA\Tag(name: "ProfileListing")]
  1125.     #[OA\Response(
  1126.         response: 200,
  1127.         description: '',
  1128.         content: new OA\JsonContent(ref: "#/components/schemas/ProfileListingPage")
  1129.     )]
  1130.     public function listWithSelfie(Request $request, City $city): Response
  1131.     {
  1132.         $specs = $this->profileListSpecificationService->listWithSelfie();
  1133.         $response = new Response();
  1134.         $result = $this->paginatedListing($city, '/city/{city}/with_selfie', ['city' => $city->getId()], $specs->spec(), null, null, $response);
  1135.         if ($this->isApiRequest($request)) {
  1136.             return $this->jsonListingResponse($result, $city, $specs);
  1137.         }
  1138.         return $this->render('ProfileList/list.html.twig', [
  1139.             'profiles' => $result,
  1140.             'source' => $this->source,
  1141.             'source_default' => self::RESULT_SOURCE_WITH_SELFIE,
  1142.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  1143.                 'city' => $city->getUriIdentity(),
  1144.                 'page' => $this->getCurrentPageNumber()
  1145.             ]),
  1146.             'recommendationSpec' => $specs->recommendationSpec(),
  1147.         ], response: $response);
  1148.     }
  1149.     /**
  1150.      * @Feature("extra_category_top_100")
  1151.      */
  1152.     #[ParamConverter("city", converter: "city_converter")]
  1153.     #[Route("/api/profiles/listing/city/{city}/category/top_100", name: "api.profile_listing.category.top_100", methods: "GET", format: "json")]
  1154.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  1155.     #[OA\Tag(name: "ProfileListing")]
  1156.     #[OA\Response(
  1157.         response: 200,
  1158.         description: '',
  1159.         content: new OA\JsonContent(ref: "#/components/schemas/ProfileListingPage")
  1160.     )]
  1161.     public function listTop100(Request $request, City $city): Response
  1162.     {
  1163.         $specs = $this->profileListSpecificationService->listApproved();
  1164.         $result = $this->top100ProfilesService->getSortedProfilesByVisits($city);
  1165.         if ($this->isApiRequest($request)) {
  1166.             return $this->jsonListingResponse($result, $city, $specs);
  1167.         }
  1168.         return $this->render('ProfileList/list.html.twig', [
  1169.             'profiles' => $result,
  1170.             'source' => self::RESULT_SOURCE_TOP_100,
  1171.             'source_default' => self::RESULT_SOURCE_TOP_100,
  1172.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  1173.                 'city' => $city->getUriIdentity(),
  1174.                 'page' => $this->getCurrentPageNumber(),
  1175.             ]),
  1176.             'recommendationSpec' => $specs->recommendationSpec(),
  1177.         ]);
  1178.     }
  1179.     /**
  1180.      * @Feature("extra_category_eromassage")
  1181.      */
  1182.     #[ParamConverter("city", converter: "city_converter")]
  1183.     #[Entity("county", expr: "repository.ofUriIdentityWithinCity(county, city)")]
  1184.     public function listByCountyEromassage(Request $request, City $city, County $county): Response
  1185.     {
  1186.         if (!$city->hasCounty($county)) {
  1187.             throw $this->createNotFoundException();
  1188.         }
  1189.         $specs = $this->profileListSpecificationService->listByCountyEromassage($county);
  1190.         $response = new Response();
  1191.         $result = $this->paginatedListing(
  1192.             $city,
  1193.             '/city/{city}/county/{county}/eromassage',
  1194.             ['city' => $city->getId(), 'county' => $county->getId()],
  1195.             $specs->spec(),
  1196.             null,
  1197.             null,
  1198.             $response
  1199.         );
  1200.         $request->attributes->set('profiles_count', $result->totalCount());
  1201.         return $this->render('ProfileList/list.html.twig', [
  1202.             'profiles' => $result,
  1203.             'source' => $this->source,
  1204.             'source_default' => self::RESULT_SOURCE_EROMASSAGE,
  1205.             'county' => $county,
  1206.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  1207.                 'city' => $city->getUriIdentity(),
  1208.                 'county' => $county->getUriIdentity(),
  1209.                 'page' => $this->getCurrentPageNumber(),
  1210.             ]),
  1211.             'recommendationSpec' => $specs->recommendationSpec(),
  1212.         ], response: $response);
  1213.     }
  1214.     /**
  1215.      * @Feature("extra_category_eromassage")
  1216.      */
  1217.     #[ParamConverter("city", converter: "city_converter")]
  1218.     #[Entity("district", expr: "repository.ofUriIdentityWithinCity(district, city)")]
  1219.     public function listByDistrictEromassage(Request $request, City $city, District $district): Response
  1220.     {
  1221.         if (!$city->hasDistrict($district)) {
  1222.             throw $this->createNotFoundException();
  1223.         }
  1224.         $specs = $this->profileListSpecificationService->listByDistrictEromassage($district);
  1225.         $response = new Response();
  1226.         $result = $this->paginatedListing(
  1227.             $city,
  1228.             '/city/{city}/district/{district}/eromassage',
  1229.             ['city' => $city->getId(), 'district' => $district->getId()],
  1230.             $specs->spec(),
  1231.             null,
  1232.             null,
  1233.             $response
  1234.         );
  1235.         $request->attributes->set('profiles_count', $result->totalCount());
  1236.         return $this->render('ProfileList/list.html.twig', [
  1237.             'profiles' => $result,
  1238.             'source' => $this->source,
  1239.             'source_default' => self::RESULT_SOURCE_EROMASSAGE,
  1240.             'district' => $district,
  1241.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  1242.                 'city' => $city->getUriIdentity(),
  1243.                 'district' => $district->getUriIdentity(),
  1244.                 'page' => $this->getCurrentPageNumber(),
  1245.             ]),
  1246.             'recommendationSpec' => $specs->recommendationSpec(),
  1247.         ], response: $response);
  1248.     }
  1249.     /**
  1250.      * @Feature("extra_category_eromassage")
  1251.      */
  1252.     #[ParamConverter("city", converter: "city_converter")]
  1253.     #[Entity("station", expr: "repository.ofUriIdentityWithinCity(station, city)")]
  1254.     public function listByStationEromassage(Request $request, City $city, Station $station): Response
  1255.     {
  1256.         if (!$city->hasStation($station)) {
  1257.             throw $this->createNotFoundException();
  1258.         }
  1259.         $specs = $this->profileListSpecificationService->listByStationEromassage($station);
  1260.         $response = new Response();
  1261.         $result = $this->paginatedListing(
  1262.             $city,
  1263.             '/city/{city}/station/{station}/eromassage',
  1264.             ['city' => $city->getId(), 'station' => $station->getId()],
  1265.             $specs->spec(),
  1266.             null,
  1267.             null,
  1268.             $response
  1269.         );
  1270.         $request->attributes->set('profiles_count', $result->totalCount());
  1271.         return $this->render('ProfileList/list.html.twig', [
  1272.             'profiles' => $result,
  1273.             'source' => $this->source,
  1274.             'source_default' => self::RESULT_SOURCE_EROMASSAGE,
  1275.             'station' => $station,
  1276.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  1277.                 'city' => $city->getUriIdentity(),
  1278.                 'station' => $station->getUriIdentity(),
  1279.                 'page' => $this->getCurrentPageNumber(),
  1280.             ]),
  1281.             'recommendationSpec' => $specs->recommendationSpec(),
  1282.         ], response: $response);
  1283.     }
  1284.     /**
  1285.      * @Feature("extra_category_verified")
  1286.      */
  1287.     #[ParamConverter("city", converter: "city_converter")]
  1288.     #[Entity("county", expr: "repository.ofUriIdentityWithinCity(county, city)")]
  1289.     public function listByCountyVerified(Request $request, City $city, County $county): Response
  1290.     {
  1291.         if (!$city->hasCounty($county)) {
  1292.             throw $this->createNotFoundException();
  1293.         }
  1294.         $specs = $this->profileListSpecificationService->listByCountyVerified($county);
  1295.         $response = new Response();
  1296.         $result = $this->paginatedListing(
  1297.             $city,
  1298.             '/city/{city}/county/{county}/proverennye',
  1299.             ['city' => $city->getId(), 'county' => $county->getId()],
  1300.             $specs->spec(),
  1301.             null,
  1302.             null,
  1303.             $response
  1304.         );
  1305.         $request->attributes->set('profiles_count', $result->totalCount());
  1306.         return $this->render('ProfileList/list.html.twig', [
  1307.             'profiles' => $result,
  1308.             'source' => $this->source,
  1309.             'source_default' => self::RESULT_SOURCE_VERIFIED,
  1310.             'county' => $county,
  1311.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  1312.                 'city' => $city->getUriIdentity(),
  1313.                 'county' => $county->getUriIdentity(),
  1314.                 'page' => $this->getCurrentPageNumber(),
  1315.             ]),
  1316.             'recommendationSpec' => $specs->recommendationSpec(),
  1317.         ], response: $response);
  1318.     }
  1319.     /**
  1320.      * @Feature("extra_category_verified")
  1321.      */
  1322.     #[ParamConverter("city", converter: "city_converter")]
  1323.     #[Entity("district", expr: "repository.ofUriIdentityWithinCity(district, city)")]
  1324.     public function listByDistrictVerified(Request $request, City $city, District $district): Response
  1325.     {
  1326.         if (!$city->hasDistrict($district)) {
  1327.             throw $this->createNotFoundException();
  1328.         }
  1329.         $specs = $this->profileListSpecificationService->listByDistrictVerified($district);
  1330.         $response = new Response();
  1331.         $result = $this->paginatedListing(
  1332.             $city,
  1333.             '/city/{city}/district/{district}/proverennye',
  1334.             ['city' => $city->getId(), 'district' => $district->getId()],
  1335.             $specs->spec(),
  1336.             null,
  1337.             null,
  1338.             $response
  1339.         );
  1340.         $request->attributes->set('profiles_count', $result->totalCount());
  1341.         return $this->render('ProfileList/list.html.twig', [
  1342.             'profiles' => $result,
  1343.             'source' => $this->source,
  1344.             'source_default' => self::RESULT_SOURCE_VERIFIED,
  1345.             'district' => $district,
  1346.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  1347.                 'city' => $city->getUriIdentity(),
  1348.                 'district' => $district->getUriIdentity(),
  1349.                 'page' => $this->getCurrentPageNumber(),
  1350.             ]),
  1351.             'recommendationSpec' => $specs->recommendationSpec(),
  1352.         ], response: $response);
  1353.     }
  1354.     /**
  1355.      * @Feature("extra_category_verified")
  1356.      */
  1357.     #[ParamConverter("city", converter: "city_converter")]
  1358.     #[Entity("station", expr: "repository.ofUriIdentityWithinCity(station, city)")]
  1359.     public function listByStationVerified(Request $request, City $city, Station $station): Response
  1360.     {
  1361.         if (!$city->hasStation($station)) {
  1362.             throw $this->createNotFoundException();
  1363.         }
  1364.         $specs = $this->profileListSpecificationService->listByStationVerified($station);
  1365.         $response = new Response();
  1366.         $result = $this->paginatedListing(
  1367.             $city,
  1368.             '/city/{city}/station/{station}/proverennye',
  1369.             ['city' => $city->getId(), 'station' => $station->getId()],
  1370.             $specs->spec(),
  1371.             null,
  1372.             null,
  1373.             $response
  1374.         );
  1375.         $request->attributes->set('profiles_count', $result->totalCount());
  1376.         return $this->render('ProfileList/list.html.twig', [
  1377.             'profiles' => $result,
  1378.             'source' => $this->source,
  1379.             'source_default' => self::RESULT_SOURCE_VERIFIED,
  1380.             'station' => $station,
  1381.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  1382.                 'city' => $city->getUriIdentity(),
  1383.                 'station' => $station->getUriIdentity(),
  1384.                 'page' => $this->getCurrentPageNumber(),
  1385.             ]),
  1386.             'recommendationSpec' => $specs->recommendationSpec(),
  1387.         ], response: $response);
  1388.     }
  1389.     /**
  1390.      * @Feature("extra_category_cheap")
  1391.      */
  1392.     #[ParamConverter("city", converter: "city_converter")]
  1393.     #[Entity("county", expr: "repository.ofUriIdentityWithinCity(county, city)")]
  1394.     public function listByCountyCheap(Request $request, City $city, County $county): Response
  1395.     {
  1396.         if (!$city->hasCounty($county)) {
  1397.             throw $this->createNotFoundException();
  1398.         }
  1399.         $specs = $this->profileListSpecificationService->listByCountyCheap($county);
  1400.         $response = new Response();
  1401.         $result = $this->paginatedListing(
  1402.             $city,
  1403.             null,
  1404.             [],
  1405.             $specs->spec(),
  1406.             null,
  1407.             null,
  1408.             $response
  1409.         );
  1410.         $request->attributes->set('profiles_count', $result->totalCount());
  1411.         return $this->render('ProfileList/list.html.twig', [
  1412.             'profiles' => $result,
  1413.             'source' => $this->source,
  1414.             'source_default' => self::RESULT_SOURCE_CHEAP,
  1415.             'county' => $county,
  1416.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  1417.                 'city' => $city->getUriIdentity(),
  1418.                 'county' => $county->getUriIdentity(),
  1419.                 'page' => $this->getCurrentPageNumber(),
  1420.             ]),
  1421.             'recommendationSpec' => $specs->recommendationSpec(),
  1422.         ], response: $response);
  1423.     }
  1424.     /**
  1425.      * @Feature("extra_category_cheap")
  1426.      */
  1427.     #[ParamConverter("city", converter: "city_converter")]
  1428.     #[Entity("district", expr: "repository.ofUriIdentityWithinCity(district, city)")]
  1429.     public function listByDistrictCheap(Request $request, City $city, District $district): Response
  1430.     {
  1431.         if (!$city->hasDistrict($district)) {
  1432.             throw $this->createNotFoundException();
  1433.         }
  1434.         $specs = $this->profileListSpecificationService->listByDistrictCheap($district);
  1435.         $response = new Response();
  1436.         $result = $this->paginatedListing(
  1437.             $city,
  1438.             null,
  1439.             [],
  1440.             $specs->spec(),
  1441.             null,
  1442.             null,
  1443.             $response
  1444.         );
  1445.         $request->attributes->set('profiles_count', $result->totalCount());
  1446.         return $this->render('ProfileList/list.html.twig', [
  1447.             'profiles' => $result,
  1448.             'source' => $this->source,
  1449.             'source_default' => self::RESULT_SOURCE_CHEAP,
  1450.             'district' => $district,
  1451.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  1452.                 'city' => $city->getUriIdentity(),
  1453.                 'district' => $district->getUriIdentity(),
  1454.                 'page' => $this->getCurrentPageNumber(),
  1455.             ]),
  1456.             'recommendationSpec' => $specs->recommendationSpec(),
  1457.         ], response: $response);
  1458.     }
  1459.     /**
  1460.      * @Feature("extra_category_cheap")
  1461.      */
  1462.     #[ParamConverter("city", converter: "city_converter")]
  1463.     #[Entity("station", expr: "repository.ofUriIdentityWithinCity(station, city)")]
  1464.     public function listByStationCheap(Request $request, City $city, Station $station): Response
  1465.     {
  1466.         if (!$city->hasStation($station)) {
  1467.             throw $this->createNotFoundException();
  1468.         }
  1469.         $specs = $this->profileListSpecificationService->listByStationCheap($station);
  1470.         $response = new Response();
  1471.         $result = $this->paginatedListing(
  1472.             $city,
  1473.             null,
  1474.             [],
  1475.             $specs->spec(),
  1476.             null,
  1477.             null,
  1478.             $response
  1479.         );
  1480.         $request->attributes->set('profiles_count', $result->totalCount());
  1481.         return $this->render('ProfileList/list.html.twig', [
  1482.             'profiles' => $result,
  1483.             'source' => $this->source,
  1484.             'source_default' => self::RESULT_SOURCE_CHEAP,
  1485.             'station' => $station,
  1486.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  1487.                 'city' => $city->getUriIdentity(),
  1488.                 'station' => $station->getUriIdentity(),
  1489.                 'page' => $this->getCurrentPageNumber(),
  1490.             ]),
  1491.             'recommendationSpec' => $specs->recommendationSpec(),
  1492.         ], response: $response);
  1493.     }
  1494.     /**
  1495.      * @Feature("extra_category_mature")
  1496.      */
  1497.     #[ParamConverter("city", converter: "city_converter")]
  1498.     #[Entity("county", expr: "repository.ofUriIdentityWithinCity(county, city)")]
  1499.     public function listByCountyMature(Request $request, City $city, County $county): Response
  1500.     {
  1501.         if (!$city->hasCounty($county)) {
  1502.             throw $this->createNotFoundException();
  1503.         }
  1504.         $specs = $this->profileListSpecificationService->listByCountyMature($county);
  1505.         $response = new Response();
  1506.         $result = $this->paginatedListing(
  1507.             $city,
  1508.             null,
  1509.             [],
  1510.             $specs->spec(),
  1511.             null,
  1512.             null,
  1513.             $response
  1514.         );
  1515.         $request->attributes->set('profiles_count', $result->totalCount());
  1516.         return $this->render('ProfileList/list.html.twig', [
  1517.             'profiles' => $result,
  1518.             'source' => $this->source,
  1519.             'source_default' => self::RESULT_SOURCE_MATURE,
  1520.             'county' => $county,
  1521.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  1522.                 'city' => $city->getUriIdentity(),
  1523.                 'county' => $county->getUriIdentity(),
  1524.                 'page' => $this->getCurrentPageNumber(),
  1525.             ]),
  1526.             'recommendationSpec' => $specs->recommendationSpec(),
  1527.         ], response: $response);
  1528.     }
  1529.     /**
  1530.      * @Feature("extra_category_mature")
  1531.      */
  1532.     #[ParamConverter("city", converter: "city_converter")]
  1533.     #[Entity("district", expr: "repository.ofUriIdentityWithinCity(district, city)")]
  1534.     public function listByDistrictMature(Request $request, City $city, District $district): Response
  1535.     {
  1536.         if (!$city->hasDistrict($district)) {
  1537.             throw $this->createNotFoundException();
  1538.         }
  1539.         $specs = $this->profileListSpecificationService->listByDistrictMature($district);
  1540.         $response = new Response();
  1541.         $result = $this->paginatedListing(
  1542.             $city,
  1543.             null,
  1544.             [],
  1545.             $specs->spec(),
  1546.             null,
  1547.             null,
  1548.             $response
  1549.         );
  1550.         $request->attributes->set('profiles_count', $result->totalCount());
  1551.         return $this->render('ProfileList/list.html.twig', [
  1552.             'profiles' => $result,
  1553.             'source' => $this->source,
  1554.             'source_default' => self::RESULT_SOURCE_MATURE,
  1555.             'district' => $district,
  1556.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  1557.                 'city' => $city->getUriIdentity(),
  1558.                 'district' => $district->getUriIdentity(),
  1559.                 'page' => $this->getCurrentPageNumber(),
  1560.             ]),
  1561.             'recommendationSpec' => $specs->recommendationSpec(),
  1562.         ], response: $response);
  1563.     }
  1564.     /**
  1565.      * @Feature("extra_category_mature")
  1566.      */
  1567.     #[ParamConverter("city", converter: "city_converter")]
  1568.     #[Entity("station", expr: "repository.ofUriIdentityWithinCity(station, city)")]
  1569.     public function listByStationMature(Request $request, City $city, Station $station): Response
  1570.     {
  1571.         if (!$city->hasStation($station)) {
  1572.             throw $this->createNotFoundException();
  1573.         }
  1574.         $specs = $this->profileListSpecificationService->listByStationMature($station);
  1575.         $response = new Response();
  1576.         $result = $this->paginatedListing(
  1577.             $city,
  1578.             null,
  1579.             [],
  1580.             $specs->spec(),
  1581.             null,
  1582.             null,
  1583.             $response
  1584.         );
  1585.         $request->attributes->set('profiles_count', $result->totalCount());
  1586.         return $this->render('ProfileList/list.html.twig', [
  1587.             'profiles' => $result,
  1588.             'source' => $this->source,
  1589.             'source_default' => self::RESULT_SOURCE_MATURE,
  1590.             'station' => $station,
  1591.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  1592.                 'city' => $city->getUriIdentity(),
  1593.                 'station' => $station->getUriIdentity(),
  1594.                 'page' => $this->getCurrentPageNumber(),
  1595.             ]),
  1596.             'recommendationSpec' => $specs->recommendationSpec(),
  1597.         ], response: $response);
  1598.     }
  1599.     /**
  1600.      * @Feature("extra_category_uzbek")
  1601.      */
  1602.     #[ParamConverter("city", converter: "city_converter")]
  1603.     #[Entity("county", expr: "repository.ofUriIdentityWithinCity(county, city)")]
  1604.     public function listByCountyUzbek(Request $request, City $city, County $county): Response
  1605.     {
  1606.         if (!$city->hasCounty($county)) {
  1607.             throw $this->createNotFoundException();
  1608.         }
  1609.         $specs = $this->profileListSpecificationService->listByCountyUzbek($county);
  1610.         $response = new Response();
  1611.         $result = $this->paginatedListing(
  1612.             $city,
  1613.             '/city/{city}/county/{county}/uzbechki',
  1614.             ['city' => $city->getId(), 'county' => $county->getId()],
  1615.             $specs->spec(),
  1616.             null,
  1617.             null,
  1618.             $response
  1619.         );
  1620.         $request->attributes->set('profiles_count', $result->totalCount());
  1621.         return $this->render('ProfileList/list.html.twig', [
  1622.             'profiles' => $result,
  1623.             'source' => $this->source,
  1624.             'source_default' => self::RESULT_SOURCE_UZBEK,
  1625.             'county' => $county,
  1626.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  1627.                 'city' => $city->getUriIdentity(),
  1628.                 'county' => $county->getUriIdentity(),
  1629.                 'page' => $this->getCurrentPageNumber(),
  1630.             ]),
  1631.             'recommendationSpec' => $specs->recommendationSpec(),
  1632.         ], response: $response);
  1633.     }
  1634.     /**
  1635.      * @Feature("extra_category_uzbek")
  1636.      */
  1637.     #[ParamConverter("city", converter: "city_converter")]
  1638.     #[Entity("district", expr: "repository.ofUriIdentityWithinCity(district, city)")]
  1639.     public function listByDistrictUzbek(Request $request, City $city, District $district): Response
  1640.     {
  1641.         if (!$city->hasDistrict($district)) {
  1642.             throw $this->createNotFoundException();
  1643.         }
  1644.         $specs = $this->profileListSpecificationService->listByDistrictUzbek($district);
  1645.         $response = new Response();
  1646.         $result = $this->paginatedListing(
  1647.             $city,
  1648.             '/city/{city}/district/{district}/uzbechki',
  1649.             ['city' => $city->getId(), 'district' => $district->getId()],
  1650.             $specs->spec(),
  1651.             null,
  1652.             null,
  1653.             $response
  1654.         );
  1655.         $request->attributes->set('profiles_count', $result->totalCount());
  1656.         return $this->render('ProfileList/list.html.twig', [
  1657.             'profiles' => $result,
  1658.             'source' => $this->source,
  1659.             'source_default' => self::RESULT_SOURCE_UZBEK,
  1660.             'district' => $district,
  1661.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  1662.                 'city' => $city->getUriIdentity(),
  1663.                 'district' => $district->getUriIdentity(),
  1664.                 'page' => $this->getCurrentPageNumber(),
  1665.             ]),
  1666.             'recommendationSpec' => $specs->recommendationSpec(),
  1667.         ], response: $response);
  1668.     }
  1669.     /**
  1670.      * @Feature("extra_category_uzbek")
  1671.      */
  1672.     #[ParamConverter("city", converter: "city_converter")]
  1673.     #[Entity("station", expr: "repository.ofUriIdentityWithinCity(station, city)")]
  1674.     public function listByStationUzbek(Request $request, City $city, Station $station): Response
  1675.     {
  1676.         if (!$city->hasStation($station)) {
  1677.             throw $this->createNotFoundException();
  1678.         }
  1679.         $specs = $this->profileListSpecificationService->listByStationUzbek($station);
  1680.         $response = new Response();
  1681.         $result = $this->paginatedListing(
  1682.             $city,
  1683.             '/city/{city}/station/{station}/uzbechki',
  1684.             ['city' => $city->getId(), 'station' => $station->getId()],
  1685.             $specs->spec(),
  1686.             null,
  1687.             null,
  1688.             $response
  1689.         );
  1690.         $request->attributes->set('profiles_count', $result->totalCount());
  1691.         return $this->render('ProfileList/list.html.twig', [
  1692.             'profiles' => $result,
  1693.             'source' => $this->source,
  1694.             'source_default' => self::RESULT_SOURCE_UZBEK,
  1695.             'station' => $station,
  1696.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  1697.                 'city' => $city->getUriIdentity(),
  1698.                 'station' => $station->getUriIdentity(),
  1699.                 'page' => $this->getCurrentPageNumber(),
  1700.             ]),
  1701.             'recommendationSpec' => $specs->recommendationSpec(),
  1702.         ], response: $response);
  1703.     }
  1704.     #[ParamConverter("city", converter: "city_converter")]
  1705.     #[Route("/api/profiles/listing/city/{city}/price/{priceType}", name: "api.profile_listing.by_price", methods: "GET", format: "json")]
  1706.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  1707.     #[OA\Parameter(name: "minPrice", description: "Min price for price type 'range'. 0 to omit.", in: "query", schema: new OA\Schema(type: "integer", default: 0, minimum: 0))]
  1708.     #[OA\Parameter(name: "maxPrice", description: "Max price for price type 'range'. 0 to omit.", in: "query", schema: new OA\Schema(type: "integer", default: 0, minimum: 0))]
  1709.     #[OA\Tag(name: "ProfileListing")]
  1710.     #[OA\Response(
  1711.         response: 200,
  1712.         description: '',
  1713.         content: new OA\JsonContent(ref: "#/components/schemas/ProfileListingPage")
  1714.     )]
  1715.     public function listByPrice(Request $request, CountryCurrencyResolver $countryCurrencyResolver, City $city, string $priceType, int $minPrice = null, int $maxPrice = null): Response
  1716.     {
  1717.         if ($this->isApiRequest($request) && 'range' === $priceType) {
  1718.             $minPrice = $request->query->getInt('minPrice');
  1719.             $maxPrice = $request->query->getInt('maxPrice');
  1720.             if (0 === $minPrice && 0 === $maxPrice) {
  1721.                 throw new \InvalidArgumentException('Either minPrice or maxPrice is required for price type "range".');
  1722.             }
  1723.         }
  1724.         $specs = $this->profileListSpecificationService->listByPrice($city, $priceType, $minPrice, $maxPrice);
  1725.         $response = new Response();
  1726.         $apiEndpoint = in_array($priceType, ['low', 'high', 'elite']) ? '/city/{city}/price/'.$priceType : null;
  1727.         $result = $this->paginatedListing($city, $apiEndpoint, ['city' => $city->getId()], $specs->spec(), null, null, $response);
  1728.         if ($this->isApiRequest($request)) {
  1729.             return $this->jsonListingResponse($result, $city, $specs);
  1730.         }
  1731.         return $this->render('ProfileList/list.html.twig', [
  1732.             'profiles' => $result,
  1733.             'source' => $this->source,
  1734.             'source_default' => self::RESULT_SOURCE_BY_PARAMS,
  1735.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  1736.                 'city' => $city->getUriIdentity(),
  1737.                 'priceType' => $priceType,
  1738.                 'minPrice' => $minPrice,
  1739.                 'maxPrice' => $maxPrice,
  1740.                 'page' => $this->getCurrentPageNumber()
  1741.             ]),
  1742.             'recommendationSpec' => $specs->recommendationSpec(),
  1743.         ], response: $response);
  1744.     }
  1745.     #[ParamConverter("city", converter: "city_converter")]
  1746.     #[Route("/api/profiles/listing/city/{city}/age/{ageType}", name: "api.profile_listing.by_age", methods: "GET", format: "json")]
  1747.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  1748.     #[OA\Tag(name: "ProfileListing")]
  1749.     #[OA\Response(
  1750.         response: 200,
  1751.         description: '',
  1752.         content: new OA\JsonContent(ref: "#/components/schemas/ProfileListingPage")
  1753.     )]
  1754.     public function listByAge(Request $request, City $city, string $ageType, int $minAge = null, int $maxAge = null): Response
  1755.     {
  1756.         $specs = $this->profileListSpecificationService->listByAge($ageType, $minAge, $maxAge);
  1757.         $response = new Response();
  1758.         $apiEndpoint = in_array($ageType, ['young', 'old']) ? '/city/{city}/age/'.$ageType : null;
  1759.         $result = $this->paginatedListing($city, $apiEndpoint, ['city' => $city->getId()], $specs->spec(), null, null, $response);
  1760.         if ($this->isApiRequest($request)) {
  1761.             return $this->jsonListingResponse($result, $city, $specs);
  1762.         }
  1763.         return $this->render('ProfileList/list.html.twig', [
  1764.             'profiles' => $result,
  1765.             'source' => $this->source,
  1766.             'source_default' => self::RESULT_SOURCE_BY_PARAMS,
  1767.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  1768.                 'city' => $city->getUriIdentity(),
  1769.                 'ageType' => $ageType,
  1770.                 'minAge' => $minAge,
  1771.                 'maxAge' => $maxAge,
  1772.                 'page' => $this->getCurrentPageNumber()
  1773.             ]),
  1774.             'recommendationSpec' => $specs->recommendationSpec(),
  1775.         ], response: $response);
  1776.     }
  1777.     #[ParamConverter("city", converter: "city_converter")]
  1778.     #[Route("/api/profiles/listing/city/{city}/height/{heightType}", name: "api.profile_listing.by_height", methods: "GET", format: "json")]
  1779.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  1780.     #[OA\Tag(name: "ProfileListing")]
  1781.     #[OA\Response(
  1782.         response: 200,
  1783.         description: '',
  1784.         content: new OA\JsonContent(ref: "#/components/schemas/ProfileListingPage")
  1785.     )]
  1786.     public function listByHeight(Request $request, City $city, string $heightType): Response
  1787.     {
  1788.         $specs = $this->profileListSpecificationService->listByHeight($heightType);
  1789.         $response = new Response();
  1790.         $result = $this->paginatedListing($city, '/city/{city}/height/'.$heightType, ['city' => $city->getId()], $specs->spec(), null, self::RESULT_SOURCE_CITY, $response);
  1791.         if ($this->isApiRequest($request)) {
  1792.             return $this->jsonListingResponse($result, $city, $specs);
  1793.         }
  1794.         return $this->render('ProfileList/list.html.twig', [
  1795.             'profiles' => $result,
  1796.             'source' => $this->source,
  1797.             'source_default' => self::RESULT_SOURCE_BY_PARAMS,
  1798.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  1799.                 'city' => $city->getUriIdentity(),
  1800.                 'heightType' => $heightType,
  1801.                 'page' => $this->getCurrentPageNumber()
  1802.             ]),
  1803.             'recommendationSpec' => $specs->recommendationSpec(),
  1804.         ], response: $response);
  1805.     }
  1806.     #[ParamConverter("city", converter: "city_converter")]
  1807.     #[Route("/api/profiles/listing/city/{city}/breasttype/{breastType}", name: "api.profile_listing.by_breast_type", methods: "GET", format: "json")]
  1808.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  1809.     #[OA\Tag(name: "ProfileListing")]
  1810.     #[OA\Response(
  1811.         response: 200,
  1812.         description: '',
  1813.         content: new OA\JsonContent(ref: "#/components/schemas/ProfileListingPage")
  1814.     )]
  1815.     public function listByBreastType(Request $request, City $city, string $breastType): Response
  1816.     {
  1817.         if (null === $type = BreastTypes::getValueByUriIdentity($breastType)) {
  1818.             throw $this->createNotFoundException();
  1819.         }
  1820.         $specs = $this->profileListSpecificationService->listByBreastType($breastType);
  1821.         $response = new Response();
  1822.         $alternativeSpec = $this->getORSpecForItemsArray(BreastTypes::getList(), function($item): ProfileWithBreastType {
  1823.             return new ProfileWithBreastType($item);
  1824.         });
  1825.         $result = $this->paginatedListing($city, '/city/{city}/breasttype/'.$type, ['city' => $city->getId()], $specs->spec(), $alternativeSpec, self::RESULT_SOURCE_BY_PARAMS, $response);
  1826.         if ($this->isApiRequest($request)) {
  1827.             return $this->jsonListingResponse($result, $city, $specs);
  1828.         }
  1829.         return $this->render('ProfileList/list.html.twig', [
  1830.             'profiles' => $result,
  1831.             'source' => $this->source,
  1832.             'source_default' => self::RESULT_SOURCE_BY_PARAMS,
  1833.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  1834.                 'city' => $city->getUriIdentity(),
  1835.                 'breastType' => $breastType,
  1836.                 'page' => $this->getCurrentPageNumber()
  1837.             ]),
  1838.             'recommendationSpec' => $specs->recommendationSpec(),
  1839.         ], response: $response);
  1840.     }
  1841.     #[ParamConverter("city", converter: "city_converter")]
  1842.     #[Route("/api/profiles/listing/city/{city}/haircolor/{hairColor}", name: "api.profile_listing.by_haircolor", methods: "GET", format: "json")]
  1843.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  1844.     #[OA\Tag(name: "ProfileListing")]
  1845.     #[OA\Response(
  1846.         response: 200,
  1847.         description: '',
  1848.         content: new OA\JsonContent(ref: "#/components/schemas/ProfileListingPage")
  1849.     )]
  1850.     public function listByHairColor(Request $request, City $city, string $hairColor): Response
  1851.     {
  1852.         if (null === $color = HairColors::getValueByUriIdentity($hairColor)) {
  1853.             throw $this->createNotFoundException();
  1854.         }
  1855.         $specs = $this->profileListSpecificationService->listByHairColor($hairColor);
  1856.         $response = new Response();
  1857.         $alternativeSpec = $this->getORSpecForItemsArray(HairColors::getList(), function($item): ProfileWithHairColor {
  1858.             return new ProfileWithHairColor($item);
  1859.         });
  1860.         $result = $this->paginatedListing($city, '/city/{city}/haircolor/'.$color, ['city' => $city->getId()], $specs->spec(), $alternativeSpec, self::RESULT_SOURCE_BY_PARAMS, $response);
  1861.         if ($this->isApiRequest($request)) {
  1862.             return $this->jsonListingResponse($result, $city, $specs);
  1863.         }
  1864.         return $this->render('ProfileList/list.html.twig', [
  1865.             'profiles' => $result,
  1866.             'source' => $this->source,
  1867.             'source_default' => self::RESULT_SOURCE_BY_PARAMS,
  1868.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  1869.                 'city' => $city->getUriIdentity(),
  1870.                 'hairColor' => $hairColor,
  1871.                 'page' => $this->getCurrentPageNumber()
  1872.             ]),
  1873.             'recommendationSpec' => $specs->recommendationSpec(),
  1874.         ], response: $response);
  1875.     }
  1876.     #[ParamConverter("city", converter: "city_converter")]
  1877.     #[Route("/api/profiles/listing/city/{city}/bodytype/{bodyType}", name: "api.profile_listing.by_bodytype", methods: "GET", format: "json")]
  1878.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  1879.     #[OA\Tag(name: "ProfileListing")]
  1880.     #[OA\Response(
  1881.         response: 200,
  1882.         description: '',
  1883.         content: new OA\JsonContent(ref: "#/components/schemas/ProfileListingPage")
  1884.     )]
  1885.     public function listByBodyType(Request $request, City $city, string $bodyType): Response
  1886.     {
  1887.         if (null === $type = BodyTypes::getValueByUriIdentity($bodyType)) {
  1888.             throw $this->createNotFoundException();
  1889.         }
  1890.         $specs = $this->profileListSpecificationService->listByBodyType($bodyType);
  1891.         $response = new Response();
  1892.         $alternativeSpec = $this->getORSpecForItemsArray(BodyTypes::getList(), function($item): ProfileWithBodyType {
  1893.             return new ProfileWithBodyType($item);
  1894.         });
  1895.         $result = $this->paginatedListing($city, '/city/{city}/bodytype/'.$type, ['city' => $city->getId()], $specs->spec(), $alternativeSpec, self::RESULT_SOURCE_BY_PARAMS, $response);
  1896.         if ($this->isApiRequest($request)) {
  1897.             return $this->jsonListingResponse($result, $city, $specs);
  1898.         }
  1899.         return $this->render('ProfileList/list.html.twig', [
  1900.             'profiles' => $result,
  1901.             'source' => $this->source,
  1902.             'source_default' => self::RESULT_SOURCE_BY_PARAMS,
  1903.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  1904.                 'city' => $city->getUriIdentity(),
  1905.                 'bodyType' => $bodyType,
  1906.                 'page' => $this->getCurrentPageNumber()
  1907.             ]),
  1908.             'recommendationSpec' => $specs->recommendationSpec(),
  1909.         ], response: $response);
  1910.     }
  1911.     #[ParamConverter("city", converter: "city_converter")]
  1912.     #[Route("/api/profiles/listing/city/{city}/place/{placeType}", name: "api.profile_listing.by_place", methods: "GET", format: "json")]
  1913.     #[Route("/api/profiles/listing/city/{city}/place/{placeType}/{takeOutLocation}", name: "api.profile_listing.by_place.take_out_location", methods: "GET", format: "json")]
  1914.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  1915.     #[OA\Tag(name: "ProfileListing")]
  1916.     #[OA\Response(
  1917.         response: 200,
  1918.         description: '',
  1919.         content: new OA\JsonContent(ref: "#/components/schemas/ProfileListingPage")
  1920.     )]
  1921.     public function listByPlace(Request $request, City $city, string $placeType, string $takeOutLocation = null): Response
  1922.     {
  1923.         if ('take-out' === $placeType && null !== $takeOutLocation && null === TakeOutLocations::getValueByUriIdentity($takeOutLocation)) {
  1924.             throw $this->createNotFoundException();
  1925.         }
  1926.         $specs = $this->profileListSpecificationService->listByPlace($placeType, $takeOutLocation);
  1927.         if (null === $specs) {
  1928.             throw $this->createNotFoundException();
  1929.         }
  1930.         $response = new Response();
  1931.         $alternativeSpec = $this->getORSpecForItemsArray(TakeOutLocations::getList(), function($item): ProfileIsProvidingTakeOut {
  1932.             return new ProfileIsProvidingTakeOut($item);
  1933.         });
  1934.         if ($placeType === 'take-out') {
  1935.             $alternativeSpec->orX(new ProfileHasApartments());
  1936.         }
  1937.         $apiEndpoint = '/city/{city}/place/'.$placeType;
  1938.         if (null !== $takeOutLocation) {
  1939.             $apiEndpoint .= '/'.$takeOutLocation;
  1940.         }
  1941.         $result = $this->paginatedListing($city, $apiEndpoint, ['city' => $city->getId()], $specs->spec(), $alternativeSpec, self::RESULT_SOURCE_BY_PARAMS, $response);
  1942.         if ($this->isApiRequest($request)) {
  1943.             return $this->jsonListingResponse($result, $city, $specs);
  1944.         }
  1945.         return $this->render('ProfileList/list.html.twig', [
  1946.             'profiles' => $result,
  1947.             'source' => $this->source,
  1948.             'source_default' => self::RESULT_SOURCE_BY_PARAMS,
  1949.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  1950.                 'city' => $city->getUriIdentity(),
  1951.                 'placeType' => $placeType,
  1952.                 'takeOutLocation' => TakeOutLocations::getUriIdentity(TakeOutLocations::getValueByUriIdentity($takeOutLocation)),
  1953.                 'page' => $this->getCurrentPageNumber()
  1954.             ]),
  1955.             'recommendationSpec' => $specs->recommendationSpec(),
  1956.         ], response: $response);
  1957.     }
  1958.     #[ParamConverter("city", converter: "city_converter")]
  1959.     #[Route("/api/profiles/listing/city/{city}/privatehaircut/{privateHaircut}", name: "api.profile_listing.by_privatehaircut", methods: "GET", format: "json")]
  1960.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  1961.     #[OA\Tag(name: "ProfileListing")]
  1962.     #[OA\Response(
  1963.         response: 200,
  1964.         description: '',
  1965.         content: new OA\JsonContent(ref: "#/components/schemas/ProfileListingPage")
  1966.     )]
  1967.     public function listByPrivateHaircut(Request $request, City $city, string $privateHaircut): Response
  1968.     {
  1969.         if(null === $type = PrivateHaircuts::getValueByUriIdentity($privateHaircut))
  1970.             throw $this->createNotFoundException();
  1971.         $specs = $this->profileListSpecificationService->listByPrivateHaircut($privateHaircut);
  1972.         $response = new Response();
  1973.         $apiEndpoint = '/city/{city}/privatehaircut/'.$type;
  1974.         $alternativeSpec = $this->getORSpecForItemsArray(PrivateHaircuts::getList(), function($item): ProfileWithPrivateHaircut {
  1975.             return new ProfileWithPrivateHaircut($item);
  1976.         });
  1977.         $result = $this->paginatedListing($city, $apiEndpoint, ['city' => $city->getId()], $specs->spec(), $alternativeSpec, self::RESULT_SOURCE_BY_PARAMS, $response);
  1978.         if ($this->isApiRequest($request)) {
  1979.             return $this->jsonListingResponse($result, $city, $specs);
  1980.         }
  1981.         return $this->render('ProfileList/list.html.twig', [
  1982.             'profiles' => $result,
  1983.             'source' => $this->source,
  1984.             'source_default' => self::RESULT_SOURCE_BY_PARAMS,
  1985.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  1986.                 'city' => $city->getUriIdentity(),
  1987.                 'privateHaircut' => $privateHaircut,
  1988.                 'page' => $this->getCurrentPageNumber()
  1989.             ]),
  1990.             'recommendationSpec' => $specs->recommendationSpec(),
  1991.         ], response: $response);
  1992.     }
  1993.     #[ParamConverter("city", converter: "city_converter")]
  1994.     #[Route("/api/profiles/listing/city/{city}/nationality/{nationality}", name: "api.profile_listing.by_nationality", methods: "GET", format: "json")]
  1995.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  1996.     #[OA\Tag(name: "ProfileListing")]
  1997.     #[OA\Response(
  1998.         response: 200,
  1999.         description: '',
  2000.         content: new OA\JsonContent(ref: "#/components/schemas/ProfileListingPage")
  2001.     )]
  2002.     public function listByNationality(Request $request, City $city, string $nationality): Response
  2003.     {
  2004.         if (null === $type = Nationalities::getValueByUriIdentity($nationality))
  2005.             throw $this->createNotFoundException();
  2006.         $specs = $this->profileListSpecificationService->listByNationality($nationality);
  2007.         $response = new Response();
  2008.         $alternativeSpec = $this->getORSpecForItemsArray(Nationalities::getList(), function($item): ProfileWithNationality {
  2009.             return new ProfileWithNationality($item);
  2010.         });
  2011.         $apiEndpoint = '/city/{city}/nationality/'.$type;
  2012.         $result = $this->paginatedListing($city, $apiEndpoint, ['city' => $city->getId()], $specs->spec(), $alternativeSpec, self::RESULT_SOURCE_BY_PARAMS, $response);
  2013.         if ($this->isApiRequest($request)) {
  2014.             return $this->jsonListingResponse($result, $city, $specs);
  2015.         }
  2016.         return $this->render('ProfileList/list.html.twig', [
  2017.             'profiles' => $result,
  2018.             'source' => $this->source,
  2019.             'source_default' => self::RESULT_SOURCE_BY_PARAMS,
  2020.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  2021.                 'city' => $city->getUriIdentity(),
  2022.                 'nationality' => $nationality,
  2023.                 'page' => $this->getCurrentPageNumber()
  2024.             ]),
  2025.             'recommendationSpec' => $specs->recommendationSpec(),
  2026.         ], response: $response);
  2027.     }
  2028.     #[ParamConverter("city", converter: "city_converter")]
  2029.     #[ParamConverter("service", options: ['mapping' => ['service' => 'uriIdentity']])]
  2030.     #[Route("/api/profiles/listing/city/{city}/service/{service}", name: "api.profile_listing.by_service", methods: "GET", format: "json")]
  2031.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  2032.     #[OA\Tag(name: "ProfileListing")]
  2033.     #[OA\Response(
  2034.         response: 200,
  2035.         description: '',
  2036.         content: new OA\JsonContent(ref: "#/components/schemas/ProfileListingPage")
  2037.     )]
  2038.     public function listByProvidedService(Request $request, City $city, Service $service): Response
  2039.     {
  2040.         $specs = $this->profileListSpecificationService->listByProvidedService($service, $city);
  2041.         $response = new Response();
  2042.         $sameGroupServices = $this->serviceRepository->findBy(['group' => $service->getGroup()]);
  2043.         $alternativeSpec = $this->getORSpecForItemsArray([$sameGroupServices], function($item): ProfileIsProvidingOneOfServices {
  2044.             return new ProfileIsProvidingOneOfServices($item);
  2045.         });
  2046.         $result = $this->paginatedListing($city, '/city/{city}/service/{service}', ['city' => $city->getId(), 'service' => $service->getId()], $specs->spec(), $alternativeSpec, self::RESULT_SOURCE_SERVICE, $response);
  2047.         if ($this->isApiRequest($request)) {
  2048.             return $this->jsonListingResponse($result, $city, $specs);
  2049.         }
  2050.         return $this->render('ProfileList/list.html.twig', [
  2051.             'profiles' => $result,
  2052.             'source' => $this->source,
  2053.             'source_default' => self::RESULT_SOURCE_SERVICE,
  2054.             'service' => $service,
  2055.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  2056.                 'city' => $city->getUriIdentity(),
  2057.                 'service' => $service->getUriIdentity(),
  2058.                 'page' => $this->getCurrentPageNumber()
  2059.             ]),
  2060.             'recommendationSpec' => $specs->recommendationSpec(),
  2061.         ], response: $response);
  2062.     }
  2063.     #[ParamConverter("city", converter: "city_converter")]
  2064.     #[ParamConverter("item", options: ['mapping' => ['item' => 'uriIdentity']])]
  2065.     #[Route("/api/profiles/listing/city/{city}/inventory-item/{item}", name: "api.profile_listing.by_inventory_item", methods: "GET", format: "json")]
  2066.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  2067.     #[OA\Tag(name: "ProfileListing")]
  2068.     #[OA\Response(
  2069.         response: 200,
  2070.         description: '',
  2071.         content: new OA\JsonContent(ref: "#/components/schemas/ProfileListingPage")
  2072.     )]
  2073.     public function listByInventoryItem(Request $request, City $city, ProfileInventoryItem $item): Response
  2074.     {
  2075.         if (!$this->isInventoryItemTypeEnabled($item)) {
  2076.             throw $this->createNotFoundException();
  2077.         }
  2078.         $specs = $this->profileListSpecificationService->listByInventoryItem($item);
  2079.         $response = new Response();
  2080.         $result = $this->paginatedListing($city, '/city/{city}/inventory-item/{item}', ['city' => $city->getId(), 'item' => $item->getId()], $specs->spec(), null, self::RESULT_SOURCE_PROFILE_INVENTORY_ITEM, $response);
  2081.         $request->attributes->set('profiles_count', $result->totalCount());
  2082.         if ($this->isApiRequest($request)) {
  2083.             return $this->json($result);
  2084.         }
  2085.         return $this->render('ProfileList/list.html.twig', [
  2086.             'profiles' => $result,
  2087.             'source' => $this->source,
  2088.             'source_default' => self::RESULT_SOURCE_PROFILE_INVENTORY_ITEM,
  2089.             'inventory_item' => $item,
  2090.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  2091.                 'city' => $city->getUriIdentity(),
  2092.                 'item' => $item->getUriIdentity(),
  2093.                 'page' => $this->getCurrentPageNumber()
  2094.             ]),
  2095.             'recommendationSpec' => $specs->recommendationSpec(),
  2096.         ], response: $response);
  2097.     }
  2098.     private function isInventoryItemTypeEnabled(ProfileInventoryItem $item): bool
  2099.     {
  2100.         return match ($item->getType()) {
  2101.             ProfileInventoryItemType::SEX_TOY => $this->features->sex_toys(),
  2102.             ProfileInventoryItemType::CLOTHES_AND_UNDERWEAR => $this->features->clothes_and_underwear(),
  2103.             ProfileInventoryItemType::ROLE_PLAYING_COSTUME => $this->features->role_playing_costumes(),
  2104.             default => false,
  2105.         };
  2106.     }
  2107.     /**
  2108.      * @Feature("has_archive_page")
  2109.      */
  2110.     #[ParamConverter("city", converter: "city_converter")]
  2111.     #[Route("/api/profiles/listing/city/{city}/archive", name: "api.profile_listing.archive", methods: "GET", format: "json")]
  2112.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  2113.     #[OA\Tag(name: "ProfileListing")]
  2114.     #[OA\Response(
  2115.         response: 200,
  2116.         description: '',
  2117.         content: new OA\JsonContent(ref: "#/components/schemas/ProfileListingPage")
  2118.     )]
  2119.     public function listArchived(Request $request, City $city): Response
  2120.     {
  2121.         $result = $this->profileList->list($city, null, null, null, false, null, ProfileList::ORDER_BY_UPDATED);
  2122.         if ($this->isApiRequest($request)) {
  2123.             return $this->jsonListingResponse($result, $city, null);
  2124.         }
  2125.         return $this->render('ProfileList/list.html.twig', [
  2126.             'profiles' => $result,
  2127.             'recommendationSpec' => new \App\Specification\ElasticSearch\ProfileIsNotArchived(), //ProfileIsArchived, согласно https://redminez.net/issues/28305 в реках выводятся неарзивные
  2128.         ]);
  2129.     }
  2130.     #[ParamConverter("city", converter: "city_converter")]
  2131.     #[Route("/api/profiles/listing/city/{city}/recent", name: "api.profile_listing.recent", methods: "GET", format: "json")]
  2132.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  2133.     #[OA\Tag(name: "ProfileListing")]
  2134.     #[OA\Response(
  2135.         response: 200,
  2136.         description: '',
  2137.         content: new OA\JsonContent(ref: "#/components/schemas/ProfileListingPage")
  2138.     )]
  2139.     public function listNew(Request $request, City $city, int $weeks = 2): Response
  2140.     {
  2141.         $specs = $this->profileListSpecificationService->listNew($weeks);
  2142.         $response = new Response();
  2143.         $result = $this->paginatedListing($city, '/city/{city}/recent', ['city' => $city->getId()], $specs->spec(), null, null, $response);
  2144.         if ($this->isApiRequest($request)) {
  2145.             return $this->jsonListingResponse($result, $city, $specs);
  2146.         }
  2147.         return $this->render('ProfileList/list.html.twig', [
  2148.             'profiles' => $result,
  2149.             'recommendationSpec' => $specs->recommendationSpec(),
  2150.         ], response: $response);
  2151.     }
  2152.     #[ParamConverter("city", converter: "city_converter")]
  2153.     #[Route("/api/profiles/listing/city/{city}/noretouch", name: "api.profile_listing.noretouch", methods: "GET", format: "json")]
  2154.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  2155.     #[OA\Tag(name: "ProfileListing")]
  2156.     #[OA\Response(
  2157.         response: 200,
  2158.         description: '',
  2159.         content: new OA\JsonContent(ref: "#/components/schemas/ProfileListingPage")
  2160.     )]
  2161.     public function listByNoRetouch(Request $request, City $city): Response
  2162.     {
  2163.         $specs = $this->profileListSpecificationService->listByNoRetouch();
  2164.         $response = new Response();
  2165.         $result = $this->paginatedListing($city, '/city/{city}/noretouch', ['city' => $city->getId()], $specs->spec(), null, self::RESULT_SOURCE_CITY, $response);
  2166.         if ($this->isApiRequest($request)) {
  2167.             return $this->jsonListingResponse($result, $city, $specs);
  2168.         }
  2169.         return $this->render('ProfileList/list.html.twig', [
  2170.             'profiles' => $result,
  2171.             'profiles_count' => $result->count(),
  2172.             'source' => $this->source,
  2173.             'recommendationSpec' => $specs->recommendationSpec(),
  2174.         ], response: $response);
  2175.     }
  2176.     #[ParamConverter("city", converter: "city_converter")]
  2177.     #[Route("/api/profiles/listing/city/{city}/nice", name: "api.profile_listing.nice", methods: "GET", format: "json")]
  2178.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  2179.     #[OA\Tag(name: "ProfileListing")]
  2180.     #[OA\Response(
  2181.         response: 200,
  2182.         description: '',
  2183.         content: new OA\JsonContent(ref: "#/components/schemas/ProfileListingPage")
  2184.     )]
  2185.     public function listByNice(Request $request, City $city): Response
  2186.     {
  2187.         $specs = $this->profileListSpecificationService->listByNice();
  2188.         $response = new Response();
  2189.         $result = $this->paginatedListing($city, '/city/{city}/nice', ['city' => $city->getId()], $specs->spec(), null, self::RESULT_SOURCE_CITY, $response);
  2190.         if ($this->isApiRequest($request)) {
  2191.             return $this->jsonListingResponse($result, $city, $specs);
  2192.         }
  2193.         return $this->render('ProfileList/list.html.twig', [
  2194.             'profiles' => $result,
  2195.             'source' => $this->source,
  2196.             'recommendationSpec' => $specs->recommendationSpec(),
  2197.         ], response: $response);
  2198.     }
  2199.     #[ParamConverter("city", converter: "city_converter")]
  2200.     #[Route("/api/profiles/listing/city/{city}/oncall", name: "api.profile_listing.oncall", methods: "GET", format: "json")]
  2201.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  2202.     #[OA\Tag(name: "ProfileListing")]
  2203.     #[OA\Response(
  2204.         response: 200,
  2205.         description: '',
  2206.         content: new OA\JsonContent(ref: "#/components/schemas/ProfileListingPage")
  2207.     )]
  2208.     public function listByOnCall(Request $request, City $city): Response
  2209.     {
  2210.         $specs = $this->profileListSpecificationService->listByOnCall();
  2211.         $response = new Response();
  2212.         $result = $this->paginatedListing($city, '/city/{city}/oncall', ['city' => $city->getId()], $specs->spec(), null, self::RESULT_SOURCE_CITY, $response);
  2213.         if ($this->isApiRequest($request)) {
  2214.             return $this->jsonListingResponse($result, $city, $specs);
  2215.         }
  2216.         return $this->render('ProfileList/list.html.twig', [
  2217.             'profiles' => $result,
  2218.             'source' => $this->source,
  2219.             'recommendationSpec' => $specs->recommendationSpec(),
  2220.         ], response: $response);
  2221.     }
  2222.     #[ParamConverter("city", converter: "city_converter")]
  2223.     #[Route("/api/profiles/listing/city/{city}/fornight", name: "api.profile_listing.fornight", methods: "GET", format: "json")]
  2224.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  2225.     #[OA\Tag(name: "ProfileListing")]
  2226.     #[OA\Response(
  2227.         response: 200,
  2228.         description: '',
  2229.         content: new OA\JsonContent(ref: "#/components/schemas/ProfileListingPage")
  2230.     )]
  2231.     public function listForNight(Request $request, City $city): Response
  2232.     {
  2233.         $specs = $this->profileListSpecificationService->listForNight();
  2234.         $response = new Response();
  2235.         $result = $this->paginatedListing($city, '/city/{city}/fornight', ['city' => $city->getId()], $specs->spec(), null, self::RESULT_SOURCE_CITY, $response);
  2236.         if ($this->isApiRequest($request)) {
  2237.             return $this->jsonListingResponse($result, $city, $specs);
  2238.         }
  2239.         return $this->render('ProfileList/list.html.twig', [
  2240.             'profiles' => $result,
  2241.             'source' => $this->source,
  2242.             'recommendationSpec' => $specs->recommendationSpec(),
  2243.         ], response: $response);
  2244.     }
  2245.     private function getSpecForEliteGirls(City $city): Filter
  2246.     {
  2247.         $minPrice = $this->countryCurrencyResolver->getValueByCountryCode($city->getCountryCode(), [
  2248.             'RUB' => 5000,
  2249.             'UAH' => 1500,
  2250.             'USD' => 100,
  2251.             'EUR' => 130,
  2252.         ]);
  2253.         return new ProfileIsElite($minPrice);
  2254.     }
  2255.     private function getElasticSearchSpecForEliteGirls(City $city): ISpecification
  2256.     {
  2257.         $minPrice = $this->countryCurrencyResolver->getValueByCountryCode($city->getCountryCode(), [
  2258.             'RUB' => 5000,
  2259.             'UAH' => 1500,
  2260.             'USD' => 100,
  2261.             'EUR' => 130,
  2262.         ]);
  2263.         return new \App\Specification\ElasticSearch\ProfileIsElite($minPrice);
  2264.     }
  2265.     #[ParamConverter("city", converter: "city_converter")]
  2266.     #[Route("/api/profiles/listing/city/{city}/elite", name: "api.profile_listing.elite", methods: "GET", format: "json")]
  2267.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  2268.     #[OA\Tag(name: "ProfileListing")]
  2269.     #[OA\Response(
  2270.         response: 200,
  2271.         description: '',
  2272.         content: new OA\JsonContent(ref: "#/components/schemas/ProfileListingPage")
  2273.     )]
  2274.     public function listForEliteGirls(CountryCurrencyResolver $countryCurrencyResolver, Request $request, City $city): Response
  2275.     {
  2276.         $specs = $this->profileListSpecificationService->listForEliteGirls($city);
  2277.         $response = new Response();
  2278.         $result = $this->paginatedListing($city, '/city/{city}/elite', ['city' => $city->getId()], $specs->spec(), null, null, $response);
  2279.         if ($this->isApiRequest($request)) {
  2280.             return $this->jsonListingResponse($result, $city, $specs);
  2281.         }
  2282.         return $this->render('ProfileList/list.html.twig', [
  2283.             'profiles' => $result,
  2284.             'source' => $this->source,
  2285.             'source_default' => self::RESULT_SOURCE_BY_PARAMS,
  2286.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  2287.                 'city' => $city->getUriIdentity(),
  2288.                 'page' => $this->getCurrentPageNumber()
  2289.             ]),
  2290.             'recommendationSpec' => $specs->recommendationSpec(),
  2291.         ], response: $response);
  2292.     }
  2293.     #[ParamConverter("city", converter: "city_converter")]
  2294.     #[Route("/api/profiles/listing/city/{city}/realelite", name: "api.profile_listing.real_elite", methods: "GET", format: "json")]
  2295.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  2296.     #[OA\Tag(name: "ProfileListing")]
  2297.     #[OA\Response(
  2298.         response: 200,
  2299.         description: '',
  2300.         content: new OA\JsonContent(ref: "#/components/schemas/ProfileListingPage")
  2301.     )]
  2302.     public function listForRealElite(Request $request, CountryCurrencyResolver $countryCurrencyResolver, City $city): Response
  2303.     {
  2304.         $specs = $this->profileListSpecificationService->listForRealElite($city);
  2305.         $response = new Response();
  2306.         $result = $this->paginatedListing($city, '/city/{city}/realelite', ['city' => $city->getId()], $specs->spec(), null, self::RESULT_SOURCE_CITY, $response);
  2307.         if ($this->isApiRequest($request)) {
  2308.             return $this->jsonListingResponse($result, $city, $specs);
  2309.         }
  2310.         return $this->render('ProfileList/list.html.twig', [
  2311.             'profiles' => $result,
  2312.             'source' => $this->source,
  2313.             'recommendationSpec' => $specs->recommendationSpec(),
  2314.         ], response: $response);
  2315.     }
  2316.     #[ParamConverter("city", converter: "city_converter")]
  2317.     #[Route("/api/profiles/listing/city/{city}/vip", name: "api.profile_listing.vip", methods: "GET", format: "json")]
  2318.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  2319.     #[OA\Tag(name: "ProfileListing")]
  2320.     #[OA\Response(
  2321.         response: 200,
  2322.         description: '',
  2323.         content: new OA\JsonContent(ref: "#/components/schemas/ProfileListingPage")
  2324.     )]
  2325.     public function listForVipPros(Request $request, CountryCurrencyResolver $countryCurrencyResolver, City $city): Response
  2326.     {
  2327.         $specs = $this->profileListSpecificationService->listForVipPros($city);
  2328.         $response = new Response();
  2329.         $result = $this->paginatedListing($city, '/city/{city}/vip', ['city' => $city->getId()], $specs->spec(), null, self::RESULT_SOURCE_CITY, $response);
  2330.         if ($this->isApiRequest($request)) {
  2331.             return $this->jsonListingResponse($result, $city, $specs);
  2332.         }
  2333.         return $this->render('ProfileList/list.html.twig', [
  2334.             'profiles' => $result,
  2335.             'source' => $this->source,
  2336.             'recommendationSpec' => $specs->recommendationSpec(),
  2337.         ], response: $response);
  2338.     }
  2339.     #[ParamConverter("city", converter: "city_converter")]
  2340.     #[Route("/api/profiles/listing/city/{city}/vipindi", name: "api.profile_listing.vipindi", methods: "GET", format: "json")]
  2341.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  2342.     #[OA\Tag(name: "ProfileListing")]
  2343.     #[OA\Response(
  2344.         response: 200,
  2345.         description: '',
  2346.         content: new OA\JsonContent(ref: "#/components/schemas/ProfileListingPage")
  2347.     )]
  2348.     public function listForVipIndividual(Request $request, CountryCurrencyResolver $countryCurrencyResolver, City $city): Response
  2349.     {
  2350.         $specs = $this->profileListSpecificationService->listForVipIndividual($city);
  2351.         $response = new Response();
  2352.         $result = $this->paginatedListing($city, '/city/{city}/vipindi', ['city' => $city->getId()], $specs->spec(), null, self::RESULT_SOURCE_CITY, $response);
  2353.         if ($this->isApiRequest($request)) {
  2354.             return $this->jsonListingResponse($result, $city, $specs);
  2355.         }
  2356.         return $this->render('ProfileList/list.html.twig', [
  2357.             'profiles' => $result,
  2358.             'source' => $this->source,
  2359.             'recommendationSpec' => $specs->recommendationSpec(),
  2360.         ], response: $response);
  2361.     }
  2362.     #[ParamConverter("city", converter: "city_converter")]
  2363.     #[Route("/api/profiles/listing/city/{city}/vipgirls", name: "api.profile_listing.vipgirls", methods: "GET", format: "json")]
  2364.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  2365.     #[OA\Tag(name: "ProfileListing")]
  2366.     #[OA\Response(
  2367.         response: 200,
  2368.         description: '',
  2369.         content: new OA\JsonContent(ref: "#/components/schemas/ProfileListingPage")
  2370.     )]
  2371.     public function listForVipGirlsCity(Request $request, City $city): Response
  2372.     {
  2373.         $specs = $this->profileListSpecificationService->listForVipGirlsCity($city);
  2374.         $response = new Response();
  2375.         $result = $this->paginatedListing($city, null, [], $specs->spec(), null, self::RESULT_SOURCE_CITY, $response);
  2376.         if ($this->isApiRequest($request)) {
  2377.             return $this->jsonListingResponse($result, $city, $specs);
  2378.         }
  2379.         return $this->render('ProfileList/list.html.twig', [
  2380.             'profiles' => $result,
  2381.             'source' => $this->source,
  2382.             'recommendationSpec' => $specs->recommendationSpec(),
  2383.         ], response: $response);
  2384.     }
  2385.     #[ParamConverter("city", converter: "city_converter")]
  2386.     #[Route("/api/profiles/listing/city/{city}/girlfriends", name: "api.profile_listing.girlfriends", methods: "GET", format: "json")]
  2387.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  2388.     #[OA\Tag(name: "ProfileListing")]
  2389.     #[OA\Response(
  2390.         response: 200,
  2391.         description: '',
  2392.         content: new OA\JsonContent(ref: "#/components/schemas/ProfileListingPage")
  2393.     )]
  2394.     public function listOfGirlfriends(Request $request, City $city): Response
  2395.     {
  2396.         $specs = $this->profileListSpecificationService->listOfGirlfriends();
  2397.         $response = new Response();
  2398.         $result = $this->paginatedListing($city, null, [], $specs->spec(), null, self::RESULT_SOURCE_CITY, $response);
  2399.         if ($this->isApiRequest($request)) {
  2400.             return $this->jsonListingResponse($result, $city, $specs);
  2401.         }
  2402.         return $this->render('ProfileList/list.html.twig', [
  2403.             'profiles' => $result,
  2404.             'source' => $this->source,
  2405.             'recommendationSpec' => $specs->recommendationSpec(),
  2406.         ]);
  2407.     }
  2408.     #[ParamConverter("city", converter: "city_converter")]
  2409.     #[Route("/api/profiles/listing/city/{city}/most_expensive", name: "api.profile_listing.most_expensive", methods: "GET", format: "json")]
  2410.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  2411.     #[OA\Tag(name: "ProfileListing")]
  2412.     #[OA\Response(
  2413.         response: 200,
  2414.         description: '',
  2415.         content: new OA\JsonContent(ref: "#/components/schemas/ProfileListingPage")
  2416.     )]
  2417.     public function listOfMostExpensive(Request $request, City $city): Response
  2418.     {
  2419.         $specs = $this->profileListSpecificationService->listOfMostExpensive($city);
  2420.         $response = new Response();
  2421.         $result = $this->paginatedListing($city, null, [], $specs->spec(), null, self::RESULT_SOURCE_CITY, $response);
  2422.         if ($this->isApiRequest($request)) {
  2423.             return $this->jsonListingResponse($result, $city, $specs);
  2424.         }
  2425.         return $this->render('ProfileList/list.html.twig', [
  2426.             'profiles' => $result,
  2427.             'source' => $this->source,
  2428.             'recommendationSpec' => $specs->recommendationSpec(),
  2429.         ]);
  2430.     }
  2431.     #[ParamConverter("city", converter: "city_converter")]
  2432.     #[Route("/api/profiles/listing/city/{city}/bdsm", name: "api.profile_listing.bdsm", methods: "GET", format: "json")]
  2433.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  2434.     #[OA\Tag(name: "ProfileListing")]
  2435.     #[OA\Response(
  2436.         response: 200,
  2437.         description: '',
  2438.         content: new OA\JsonContent(ref: "#/components/schemas/ProfileListingPage")
  2439.     )]
  2440.     public function listBdsm(Request $request, City $city, ServiceRepository $serviceRepository, ParameterBagInterface $parameterBag): Response
  2441.     {
  2442.         $specs = $this->profileListSpecificationService->listBdsm();
  2443.         $response = new Response();
  2444.         $result = $this->paginatedListing($city, '/city/{city}/bdsm', ['city' => $city->getId()], $specs->spec(), null, null, $response);
  2445.         if ($this->isApiRequest($request)) {
  2446.             return $this->jsonListingResponse($result, $city, $specs);
  2447.         }
  2448.         return $this->render('ProfileList/list.html.twig', [
  2449.             'profiles' => $result,
  2450.             'recommendationSpec' => $specs->recommendationSpec(),
  2451.         ], response: $response);
  2452.     }
  2453.     #[ParamConverter("city", converter: "city_converter")]
  2454.     #[Route("/api/profiles/listing/city/{city}/gender/{gender}", name: "api.profile_listing.by_gender", methods: "GET", format: "json")]
  2455.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  2456.     #[OA\Tag(name: "ProfileListing")]
  2457.     #[OA\Response(
  2458.         response: 200,
  2459.         description: '',
  2460.         content: new OA\JsonContent(ref: "#/components/schemas/ProfileListingPage")
  2461.     )]
  2462.     public function listByGender(Request $request, City $city, string $gender, DefaultCityProvider $defaultCityProvider): Response
  2463.     {
  2464.         if ($city->getId() != $defaultCityProvider->getDefaultCity()->getId()) {
  2465.             throw $this->createNotFoundException();
  2466.         }
  2467.         if (null === Genders::getValueByUriIdentity($gender))
  2468.             throw $this->createNotFoundException();
  2469.         $specs = $this->profileListSpecificationService->listByGender($gender);
  2470.         $response = new Response();
  2471.         $result = $this->paginatedListing($city, null, [], $specs->spec(), null, null, $response);
  2472.         if ($this->isApiRequest($request)) {
  2473.             return $this->jsonListingResponse($result, $city, $specs);
  2474.         }
  2475.         return $this->render('ProfileList/list.html.twig', [
  2476.             'profiles' => $result,
  2477.             'recommendationSpec' => $specs->recommendationSpec(),
  2478.         ]);
  2479.     }
  2480.     /**
  2481.      * Сейчас не используется, решили доставать их всех соседних подкатегорий разом.
  2482.      * Пока оставил, вдруг передумают.
  2483.      * @deprecated
  2484.      */
  2485.     public function listByNextSimilarCategories(callable $listMethod, $requestCategory, array $similarItems): ORMQueryResult
  2486.     {
  2487.         $similarItems = array_filter($similarItems, function ($item) use ($requestCategory): bool {
  2488.             return $item != $requestCategory;
  2489.         });
  2490.         //shuffle($similarItems);
  2491.         $item = null;
  2492.         $result = null;
  2493.         do {
  2494.             $item = $item == null ? current($similarItems) : next($similarItems);
  2495.             if (false === $item)
  2496.                 return $result;
  2497.             $result = $listMethod($item);
  2498.         } while ($result->count() == 0);
  2499.         return $result;
  2500.     }
  2501.     private function shouldShowHomepageCityListingsBlock(City $city, int $page, bool $subRequest): bool
  2502.     {
  2503.         if ($page !== 1) {
  2504.             return false;
  2505.         }
  2506.         if ($subRequest) {
  2507.             return true;
  2508.         }
  2509.         return !$city->equals($this->parameterBag->get('default_city'));
  2510.     }
  2511.     protected function getCurrentPageNumber(): int
  2512.     {
  2513.         $page = (int) $this->requestStack->getCurrentRequest()?->get($this->pageParameter, 1);
  2514.         if ($page < 1) {
  2515.             $page = 1;
  2516.         }
  2517.         return $page;
  2518.     }
  2519.     protected function render(string $view, array $parameters = [], Response $response = null): Response
  2520.     {
  2521.         $parameters = $this->fillListingWithRecommendations($parameters);
  2522.         $this->listingService->setCurrentListingPage($parameters['profiles']);
  2523.         $requestAttrs = $this->requestStack->getCurrentRequest();
  2524.         if (isset($parameters['profiles']) && is_object($parameters['profiles']) && method_exists($parameters['profiles'], 'totalCount')) {
  2525.             $requestAttrs->attributes->set('profiles_count', $parameters['profiles']->totalCount());
  2526.         }
  2527.         $listing = $requestAttrs->get('_controller');
  2528.         $listing = is_array($listing) ? $listing[count($listing) - 1] : $listing;
  2529.         $listing = preg_replace('/[^:]+::/', '', $listing);
  2530.         $listingParameters = $requestAttrs->get('_route_params');
  2531.         $listingParameters = is_array($listingParameters) ? $listingParameters : [];
  2532.         $mainRequestHasPageParam = isset(($this->requestStack->getMainRequest()->get('_route_params') ?? [])['page']);
  2533.         if ($this->requestStack->getCurrentRequest()->isXmlHttpRequest()) {
  2534.             $view = (
  2535.                 str_starts_with($listing, 'list')
  2536.                 && 'ProfileList/list.html.twig' === $view
  2537.                 && $mainRequestHasPageParam //isset($listingParameters['page'])
  2538.             )
  2539.                 ? 'ProfileList/list.profiles.html.twig'
  2540.                 : $view;
  2541.             return $this->prepareForXhr(parent::render($view, $parameters, $response));
  2542.             //return $this->getJSONResponse($parameters);
  2543.         } else {
  2544.             $parameters = $this->prepareSsrRecommendedProfiles($parameters);
  2545.             $parameters = array_merge($parameters, [
  2546.                 'listing' => $listing,
  2547.                 'listing_parameters' => $listingParameters,
  2548.             ]);
  2549.             return parent::render($view, $parameters, $response);
  2550.         }
  2551.     }
  2552.     private function fillListingWithRecommendations(array $parameters): array
  2553.     {
  2554.         $parameters['recommendations_fill_applied'] = $parameters['recommendations_fill_applied'] ?? false;
  2555.         if (!($parameters['profiles'] ?? null) instanceof Page) {
  2556.             return $parameters;
  2557.         }
  2558.         $city = $this->requestStack->getCurrentRequest()?->attributes->get('city');
  2559.         if (!$city instanceof City) {
  2560.             return $parameters;
  2561.         }
  2562.         $filled = $this->profileListingRecommendationsFiller->fill(
  2563.             $city,
  2564.             $parameters['profiles'],
  2565.             $this->resolveRecommendationGenders(),
  2566.         );
  2567.         $parameters['profiles'] = $filled['profiles'];
  2568.         $parameters['recommendations_fill_applied'] = $filled['applied'];
  2569.         $parameters['recommendations_fill_original_count'] = $filled['original_count'];
  2570.         $parameters['excludeRecommendationProfileIds'] = array_values(array_unique(array_merge(
  2571.             $parameters['excludeRecommendationProfileIds'] ?? [],
  2572.             $filled['visible_profile_ids'] ?? [],
  2573.         )));
  2574.         return $parameters;
  2575.     }
  2576.     private function prepareSsrRecommendedProfiles(array $parameters): array
  2577.     {
  2578.         if (!empty($parameters['recommendations_fill_applied'])) {
  2579.             return $parameters;
  2580.         }
  2581.         if (isset($parameters['ssr_recommended_profiles']) || !($parameters['profiles'] ?? null) instanceof Page) {
  2582.             return $parameters;
  2583.         }
  2584.         $city = $this->requestStack->getCurrentRequest()?->attributes->get('city');
  2585.         if (!$city instanceof City) {
  2586.             return $parameters;
  2587.         }
  2588.         $parameters['ssr_recommended_profiles'] = $this->profileListingRecommendationsFiller->popularProfilesForBlock(
  2589.             $city,
  2590.             $parameters['profiles'],
  2591.             $this->resolveRecommendationGenders(),
  2592.             5,
  2593.             $parameters['excludeRecommendationProfileIds'] ?? [],
  2594.         );
  2595.         return $parameters;
  2596.     }
  2597.     private function resolveRecommendationGenders(): array
  2598.     {
  2599.         $gender = $this->requestStack->getCurrentRequest()?->attributes->get('gender');
  2600.         if (is_string($gender) && null !== Genders::getValueByUriIdentity($gender)) {
  2601.             return [Genders::getValueByUriIdentity($gender)];
  2602.         }
  2603.         return [Genders::FEMALE];
  2604.     }
  2605.     private function listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacementLimited(
  2606.         City $city,
  2607.         ?Filter $spec,
  2608.         array $additionalSpecs = null,
  2609.         array $genders = [Genders::FEMALE],
  2610.         int $limit = 0,
  2611.     ): array|Page {
  2612.         return $this->profileList->listActiveWithinCityOrderedByStatusWithSpecLimited($city, $spec, $additionalSpecs, $genders, true, $limit);
  2613.     }
  2614.     private function listRandomSinglePage(
  2615.         City $city,
  2616.         ?string $country,
  2617.         ?Filter $spec,
  2618.         ?array $additionalSpecs,
  2619.         bool $active,
  2620.         ?bool $masseur = false,
  2621.         array $genders = [Genders::FEMALE]
  2622.     ): Page {
  2623.         return $this->profileList->listRandom($city, $country, $spec, $additionalSpecs, $active, $masseur, $genders, true);
  2624.     }
  2625.     private function shuffleProfilesOnPage(Page $result): Page
  2626.     {
  2627.         $profiles = iterator_to_array($result->getIterator());
  2628.         if(count($profiles) > 1) {
  2629.             shuffle($profiles);
  2630.         }
  2631.         return new FakeORMQueryPage(
  2632.             $result->getCurrentOffset(),
  2633.             $result->getCurrentPage(),
  2634.             $result->getCurrentLimit(),
  2635.             $result->totalCount(),
  2636.             $profiles
  2637.         );
  2638.     }
  2639.     /**
  2640.      * Достает из списка анкет их id с учетом совместимости разных форматов данных
  2641.      */
  2642.     private function extractProfileIds(array $profiles): array
  2643.     {
  2644.         $ids = array_map(static function ($item) {
  2645.             /**
  2646.              * - array - данные из микросервиса ротации через API
  2647.              * - Profile::getId() - полноценная сущность анкеты
  2648.              * - ProfileListingReadModel::$id - read-model анкеты
  2649.              */
  2650.             return is_array($item) ? $item['id'] : ($item?->id ?? $item?->getId());
  2651.         }, $profiles);
  2652.         return array_filter($ids); // remove null values
  2653.     }
  2654. //    protected function getJSONResponse(array $parameters)
  2655. //    {
  2656. //        $request = $this->request;
  2657. //        $data = json_decode($request->getContent(), true);
  2658. //
  2659. //        $imageSize = !empty($data['imageSize']) ? $data['imageSize'] : "357x500";
  2660. //
  2661. //        /** @var FakeORMQueryPage $queryPage */
  2662. //        $queryPage = $parameters['profiles'];
  2663. //
  2664. //        $profiles = array_map(function(ProfileListingReadModel $profile) use ($imageSize) {
  2665. //            $profile->stations = array_values($profile->stations);
  2666. //            $profile->avatar['path'] = $this->responsiveAssetsService->getResponsiveImageUrl($profile->avatar['path'], 'profile_media', $imageSize, 'jpg');
  2667. //            $profile->uri = $this->generateUrl('profile_preview.page', ['city' => $profile->city->uriIdentity, 'profile' => $profile->uriIdentity]);
  2668. //            return $profile;
  2669. //        }, $queryPage->getArray());
  2670. //
  2671. //        return new JsonResponse([
  2672. //            'profiles' => $profiles,
  2673. //            'currentPage' => $queryPage->getCurrentPage(),
  2674. //        ], Response::HTTP_OK);
  2675. //    }
  2676. }