src/Aqarmap/Bundle/SearchBundle/Controller/Api/V4/ListingSearchController.php line 592

  1. if (200 === $response->getStatusCode()) {
  2. $data = $response->toArray();
  3. if (isset($data['data']) && is_array($data['data'])) {
  4. foreach ($data['data'] as &$item) {
  5. if (isset($item['listings']) && is_array($item['listings'])) {
  6. $listings = $listingRepository->getListingsByIds(array_column($item['listings'], 'id'))->getQuery()->getResult();
  7. $context = SerializationContext::create()
  8. ->setGroups(['DefaultV4', 'SearchV4']);
  9. $item['listings'] = json_decode(
  10. $jmsSerializer->serialize($listings, 'json', $context),
  11. true
  12. );
  13. }
  14. }
  15. }
  16. return $this->json($data);
  17. }
  18. return $this->json([
  19. 'error' => 'Failed to fetch nearest search listings',
  20. ], $response->getStatusCode());
  21. }
  22. #[Rest\Get('/api/v4/listings', name: 'aqarmap_api_get_listings', options: ['i18n' => false])]
  23. #[Rest\Get('/api/v4/listings/search', name: 'aqarmap_api_listings_search_v4', options: ['i18n' => false])]
  24. #[Rest\QueryParam(name: 'personalizedSearch', requirements: '(1)|(0)', nullable: true, strict: true, description: 'Enable personalized search', default: '0')]
  25. #[OA\Parameter(name: 'personalizedSearch', in: 'query', description: 'Enable personalized search (requires active subscription)', required: false)]
  26. public function getListings(Request $request, ListingRepository $listingRepository, ListingManager $listingManager, CacheInterface $cache, MessageBusInterface $messageBus)
  27. {
  28. /** @var UserInterface $user */
  29. $user = $this->getUser();
  30. $hasSearchScoringRole = $user && $user->hasRole('ROLE_SEARCH_SCORING');
  31. if (!$hasSearchScoringRole && ($request->get('esdebug') || $request->get('scoredebug'))) {
  32. $request->query->remove('esdebug');
  33. $request->query->remove('scoredebug');
  34. }
  35. if ($request->get('location')) {
  36. $request->query->set('locations', explode(',', $request->query->get('location', '')));
  37. }
  38. $personalizedSearch = $request->query->getBoolean('personalizedSearch', false);
  39. if ($personalizedSearch) {
  40. if (!$user instanceof User) {
  41. throw new UnauthorizedHttpException('Personalized search requires an active session. Please log in.');
  42. }
  43. if (!$user->hasSubscriptionPlan()) {
  44. throw new AccessDeniedHttpException('Active subscription required for personalized search.');
  45. }
  46. $request->query->set('personalizedSearch', true);
  47. $request->query->set('personalizedForUser', $user->getId());
  48. }
  49. if ($personalizedSearch) {
  50. $data = $this->getSerializedListingSearchResults($request, $listingRepository, $listingManager);
  51. } else {
  52. $cacheKey = sprintf('api_listings_search_v4_%s_%s', $request->getLocale(), md5(http_build_query($request->query->all())));
  53. $data = $cache->get(
  54. $cacheKey,
  55. function(ItemInterface $item) use ($request, $listingRepository, $listingManager) {
  56. $item->expiresAfter(3600 * 3);
  57. return $this->getSerializedListingSearchResults($request, $listingRepository, $listingManager);
  58. }
  59. );
  60. }
  61. return new Response($data);
  62. }
  63. private function getSerializedListingSearchResults(Request $request, ListingRepository $listingRepository, ListingManager $listingManager): string
  64. {
  65. $listingsElasticResponse = $listingManager->getListingsElasticResponse($request);
  66. $listingsQueryBuilder = $listingRepository->getListingsByIds(array_column($listingsElasticResponse['items'], 'id'));
  67. if ('aqarmap_api_listings_search_v4' === $request->attributes->get('_route')) {
  68. $listingsElasticResponse['pagination']['totalPages'] = (float) $listingsElasticResponse['pagination']['totalPages'];
  69. }
  70. $listings = $listingsQueryBuilder->getQuery()->getResult();
  71. $data = [
  72. 'default' => $this->mapPaginatedBody($listingsElasticResponse, $listings),
  73. 'related' => [],
  74. ];
  75. return $this->serializer->serialize(
  76. $data,
  77. 'json',
  78. SerializationContext::create()
  79. ->setGroups('SearchV4')
  80. ->enableMaxDepthChecks()
  81. );
  82. }
  83. #[Rest\Get('/api/v4/listings/debug', options: ['i18n' => false], name: 'aqarmap_api_get_listings_debug')]
  84. public function getListingsEsDebug(Request $request, ListingManager $listingManager)
  85. {
  86. /** @var UserInterface $user */
  87. $user = $this->getUser();
  88. if (!($user && $user->hasRole('ROLE_SEARCH_SCORING'))) {
  89. throw new UnauthorizedHttpException();
  90. }
  91. $request->query->set('esdebug', 1);
  92. if ($request->get('location')) {
  93. $request->query->set('locations', explode(',', $request->query->get('location', '')));
  94. }
  95. $listingsDebugElasticResponse = $listingManager->getListingsDebugElasticResponse($request);
  96. if ('aqarmap_api_listings_search_v4' === $request->attributes->get('_route')) {
  97. $listingsDebugElasticResponse['pagination']['totalPages'] = (float) $listingsDebugElasticResponse['pagination']['totalPages'];
  98. }
  99. $data = [
  100. 'default' => $this->mapPaginatedBody($listingsDebugElasticResponse, $listingsDebugElasticResponse['items']),
  101. ];
  102. return new JsonResponse($data);
  103. }
  104. #[Rest\Get('/api/v4/listings/search/ssr-data', name: 'aqarmap_api_get_listings_search_ssr-data', options: ['i18n' => false])]
  105. public function getListingsSearchSSRData(Request $request, ListingManager $listingManager, CacheInterface $cache, SectionService $sectionService)
  106. {
  107. $cacheKey = sprintf('api_listings_search_ssr_data_%s_%s', $request->getLocale(), md5(http_build_query($request->query->all())));
  108. $cachedResponse = $cache->getItem($cacheKey);
  109. if (!$cachedResponse->isHit() || empty($cachedResponse->get())) {
  110. $customParagraph = $locationChildren = $slugResolver = $faqData = $locationParents = [];
  111. if ($request->get('location')) {
  112. $request->query->set('locations', explode(',', $request->query->get('location', '')));
  113. }
  114. $location = $request->query->all('locations') ? $request->query->all('locations')[0] : null;
  115. $location = $this->locationRepository->findOneBy(['slug' => $location]);
  116. $section = $this->sectionRepository->findOneBy(['slug' => $request->query->get('section')]);
  117. $propertyType = $this->propertyTypeRepository->findOneBy(['slug' => $request->query->get('propertyType')]);
  118. if ($location) {
  119. $locationChildren = $listingManager->getSerializedLocationChildren($location);
  120. $locationParents = $listingManager->getLocationParents($location, $request->getLocale());
  121. }
  122. if ($section && $propertyType && $location) {
  123. $longTail = $this->seoListingSearchService->getSerializedLongTailData($section, $propertyType, $location) ?? [];
  124. }
  125. if ($section && $propertyType) {
  126. $byOwnerOnly = filter_var($request->get('byOwnerOnly'), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) ?? false;
  127. $customParagraph = $listingManager->getSerializedCustomParagraph($section, $propertyType, $location, $byOwnerOnly) ?? [];
  128. $slugResolver = $listingManager->getSerializedResolvedSlugs($section, $propertyType, $request->query->all('locations'));
  129. $faqData = $listingManager->getFaqs($section, $propertyType, $location, $request->getLocale());
  130. }
  131. $response = [
  132. 'locationChildren' => !empty($locationChildren) ? json_decode((string) $locationChildren, true) : [],
  133. 'longTail' => !empty($longTail) ? json_decode((string) $longTail, true) : [],
  134. 'customParagraph' => !empty($customParagraph) ? json_decode((string) $customParagraph, true) : [],
  135. 'slugResolver' => !empty($slugResolver) ? json_decode((string) $slugResolver, true) : [],
  136. 'faqData' => $faqData,
  137. 'locationParents' => $locationParents,
  138. 'sections' => json_decode((string) $sectionService->getSerializedSections(), true),
  139. 'propertyTypeChips' => $listingManager->getListingPropertyTypesChips($request, $location, $section, $propertyType),
  140. ];
  141. $cachedResponse->set(json_encode($response));
  142. $cachedResponse->expiresAfter(3600 * 3);
  143. $cache->save($cachedResponse);
  144. }
  145. return new JsonResponse(json_decode((string) $cachedResponse->get(), true));
  146. }
  147. #[Rest\Get('/api/v4/listings/trigger-search', options: ['i18n' => false], name: 'aqarmap_api_trigger_search_listings_v4')]
  148. public function triggerSearchListings(Request $request, MessageBusInterface $messageBus)
  149. {
  150. $user = $this->getUser();
  151. if ($request->get('location')) {
  152. $request->query->set('locations', explode(',', $request->query->get('location', '')));
  153. }
  154. if ($request->get('keywordSearch')) {
  155. $this->eventDispatcher->dispatch(new SearchTriggerEvent($request));
  156. }
  157. if ($user) {
  158. $messageBus->dispatch(new Search($request->query, $user));
  159. }
  160. return new JsonResponse([
  161. 'statusCode' => Response::HTTP_OK,
  162. 'statusMessage' => 'Search triggered successfully!',
  163. ]);
  164. }
  165. private function mapPaginatedBody(array $result, array $listings): array
  166. {
  167. if (!$result['pagination']) {
  168. return [
  169. 'statusCode' => $this->getStatusCode(),
  170. 'statusMessage' => $this->getStatusMessage(),
  171. 'paginate' => [],
  172. 'data' => [],
  173. 'errors' => $this->getErrors(),
  174. ];
  175. }
  176. return [
  177. 'statusCode' => $this->getStatusCode(),
  178. 'statusMessage' => $this->getStatusMessage(),
  179. 'paginate' => $result['pagination'],
  180. 'data' => $listings,
  181. 'errors' => $this->getErrors(),
  182. ];
  183. }
  184. }