src/Controller/ProfileListController.php line 1972

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