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

  1. $mapped->setMapListingAttributes(true);
  2. $mapped->setMapListingMainPhoto(true);
  3. $mapped->setMapListingPhotos(true);
  4. $data = [
  5. 'default' => $this->makeMappedPaginatedBody($result['searchResults'], $mapped),
  6. 'related' => [],
  7. ];
  8. $data = $this->serializer->serialize(
  9. $data,
  10. 'json',
  11. SerializationContext::create()
  12. ->setGroups('SearchV4')
  13. ->enableMaxDepthChecks()
  14. );
  15. $cachedSerialize->set($data);
  16. $cachedSerialize->expiresAfter(3600 * 3);
  17. $cache->save($cachedSerialize);
  18. }
  19. return new Response($cachedSerialize->get());
  20. }
  21. /**
  22. * Related Listings Search V4.
  23. *
  24. * @return View
  25. *
  26. * @throws \Exception
  27. */
  28. #[Rest\Get('/api/v4/listings/search/related', options: ['i18n' => false], name: 'aqarmap_api_get_related_results_v4')]
  29. #[Rest\QueryParam(name: 'propertyType', requirements: '\d+', default: null, description: 'Property Type ID')]
  30. #[Rest\QueryParam(name: 'location', requirements: '\d+', default: null, description: 'Location ID comma spereted')]
  31. #[Rest\QueryParam(name: 'section', requirements: '\d+', default: null, description: 'Section ID')]
  32. #[Rest\QueryParam(name: 'page', requirements: '\d+', nullable: true, default: 1, description: 'Page number, starting from 1.')]
  33. #[Rest\QueryParam(name: 'limit', requirements: '\d+', nullable: true, default: 10, description: 'Number of items per page.')]
  34. #[Rest\QueryParam(name: 'sort', requirements: 'price|area', nullable: true, default: null, description: 'Sort search results by price or area.')]
  35. #[Rest\QueryParam(name: 'direction', requirements: 'asc|desc', nullable: true, default: 'asc', description: 'Ascending (A to Z, 0 to 9), Descending (Z to A, 9 to 0)')]
  36. #[Rest\View(serializerGroups: ['DefaultV4', 'SearchV4'])]
  37. #[OA\Parameter(name: 'propertyType', in: 'query', description: 'Property Type ID', required: false)]
  38. #[OA\Parameter(name: 'location', in: 'query', description: 'Location ID comma spereted', required: false)]
  39. #[OA\Parameter(name: 'section', in: 'query', description: 'Section ID', required: false)]
  40. #[OA\Parameter(name: 'page', in: 'query', description: 'Page number, starting from 1.', required: false)]
  41. #[OA\Parameter(name: 'limit', in: 'query', description: 'Number of items per page.', required: false)]
  42. #[OA\Parameter(name: 'sort', in: 'query', description: 'Sort search results by price or area.', required: false)]
  43. #[OA\Parameter(name: 'direction', in: 'query', description: 'Ascending (A to Z, 0 to 9), Descending (Z to A, 9 to 0)', required: false)]
  44. #[OA\Response(response: 500, description: 'Returned when something went wrong, for example if you entered non existing propertyType ID')]
  45. public function getRelatedSearchListings(Request $request, HttpClientInterface $searchClient, SerializerInterface $jmsSerializer, ListingRepository $listingRepository)
  46. {
  47. $response = $searchClient->request('GET', '/api/listing/nearest', [
  48. 'query' => $request->query->all(),
  49. 'headers' => [
  50. 'Accept-Language' => $request->getPreferredLanguage(),
  51. ],
  52. ]);
  53. if (200 === $response->getStatusCode()) {
  54. $data = $response->toArray();
  55. if (isset($data['data']) && is_array($data['data'])) {
  56. foreach ($data['data'] as &$item) {
  57. if (isset($item['listings']) && is_array($item['listings'])) {
  58. $listings = $listingRepository->getListingsByIds(array_column($item['listings'], 'id'))->getQuery()->getResult();
  59. $context = SerializationContext::create()
  60. ->setGroups(['DefaultV4', 'SearchV4']);
  61. $item['listings'] = json_decode(
  62. $jmsSerializer->serialize($listings, 'json', $context),
  63. true
  64. );
  65. }
  66. }
  67. }
  68. return $this->json($data);
  69. }
  70. return $this->json([
  71. 'error' => 'Failed to fetch nearest search listings',
  72. ], $response->getStatusCode());
  73. }
  74. #[Rest\Get('/api/v4/listings', options: ['i18n' => false], name: 'aqarmap_api_get_listings')]
  75. #[Rest\Get('/api/v4/listings/search', options: ['i18n' => false], name: 'aqarmap_api_listings_search_v4')]
  76. public function getListings(Request $request, ListingRepository $listingRepository, ListingManager $listingManager, CacheInterface $cache, MessageBusInterface $messageBus)
  77. {
  78. /** @var UserInterface $user */
  79. $user = $this->getUser();
  80. $hasSearchScoringRole = $user && $user->hasRole('ROLE_SEARCH_SCORING');
  81. if (!$hasSearchScoringRole && ($request->get('esdebug') || $request->get('scoredebug'))) {
  82. $request->query->remove('esdebug');
  83. $request->query->remove('scoredebug');
  84. }
  85. if ($request->get('location')) {
  86. $request->query->set('locations', explode(',', $request->query->get('location', '')));
  87. }
  88. $cacheKey = sprintf('api_listings_search_v4_%s_%s', $request->getLocale(), md5(http_build_query($request->query->all())));
  89. $cachedSerialize = $cache->getItem($cacheKey);
  90. if (!$cachedSerialize->isHit() || empty($cachedSerialize->get())) {
  91. $listingsElasticResponse = $listingManager->getListingsElasticResponse($request);
  92. $listingsQueryBuilder = $listingRepository->getListingsByIds(array_column($listingsElasticResponse['items'], 'id'));
  93. if ('aqarmap_api_listings_search_v4' === $request->attributes->get('_route')) {
  94. $listingsElasticResponse['pagination']['totalPages'] = (float) $listingsElasticResponse['pagination']['totalPages'];
  95. }
  96. $listings = $listingsQueryBuilder->getQuery()->getResult();
  97. $data = [
  98. 'default' => $this->mapPaginatedBody($listingsElasticResponse, $listings),
  99. 'related' => [],
  100. ];
  101. $data = $this->serializer->serialize(
  102. $data,
  103. 'json',
  104. SerializationContext::create()
  105. ->setGroups('SearchV4')
  106. ->enableMaxDepthChecks()
  107. );
  108. $cachedSerialize->set($data);
  109. $cachedSerialize->expiresAfter(3600 * 3);
  110. $cache->save($cachedSerialize);
  111. }
  112. return new Response($cachedSerialize->get());
  113. }
  114. #[Rest\Get('/api/v4/listings/debug', options: ['i18n' => false], name: 'aqarmap_api_get_listings_debug')]
  115. public function getListingsEsDebug(Request $request, ListingManager $listingManager)
  116. {
  117. /** @var UserInterface $user */
  118. $user = $this->getUser();
  119. if (!($user && $user->hasRole('ROLE_SEARCH_SCORING'))) {
  120. throw new UnauthorizedHttpException();
  121. }
  122. $request->query->set('esdebug', 1);
  123. if ($request->get('location')) {
  124. $request->query->set('locations', explode(',', $request->query->get('location', '')));
  125. }
  126. $listingsDebugElasticResponse = $listingManager->getListingsDebugElasticResponse($request);
  127. if ('aqarmap_api_listings_search_v4' === $request->attributes->get('_route')) {
  128. $listingsDebugElasticResponse['pagination']['totalPages'] = (float) $listingsDebugElasticResponse['pagination']['totalPages'];
  129. }
  130. $data = [
  131. 'default' => $this->mapPaginatedBody($listingsDebugElasticResponse, $listingsDebugElasticResponse['items']),
  132. ];
  133. return new JsonResponse($data);
  134. }
  135. #[Rest\Get('/api/v4/listings/search/ssr-data', options: ['i18n' => false], name: 'aqarmap_api_get_listings_search_ssr-data')]
  136. public function getListingsSearchSSRData(Request $request, ListingManager $listingManager, CacheInterface $cache, SectionService $sectionService)
  137. {
  138. $cacheKey = sprintf('api_listings_search_ssr_data_%s_%s', $request->getLocale(), md5(http_build_query($request->query->all())));
  139. $cachedResponse = $cache->getItem($cacheKey);
  140. if (!$cachedResponse->isHit() || empty($cachedResponse->get())) {
  141. $customParagraph = $locationChildren = $slugResolver = $faqData = $locationParents = [];
  142. if ($request->get('location')) {
  143. $request->query->set('locations', explode(',', $request->query->get('location', '')));
  144. }
  145. $location = $request->get('locations') ? $request->get('locations')[0] : null;
  146. $location = $this->locationRepository->findOneBy(['slug' => $location]);
  147. $section = $this->sectionRepository->findOneBy(['slug' => $request->query->get('section')]);
  148. $propertyType = $this->propertyTypeRepository->findOneBy(['slug' => $request->query->get('propertyType')]);
  149. if ($location) {
  150. $locationChildren = $listingManager->getSerializedLocationChildren($location);
  151. $locationParents = $listingManager->getLocationParents($location, $request->getLocale());
  152. }
  153. if ($section && $propertyType && $location) {
  154. $longTail = $this->seoListingSearchService->getSerializedLongTailData($section, $propertyType, $location) ?? [];
  155. }
  156. if ($section && $propertyType) {
  157. $byOwnerOnly = filter_var($request->get('byOwnerOnly'), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) ?? false;
  158. $customParagraph = $listingManager->getSerializedCustomParagraph($section, $propertyType, $location, $byOwnerOnly) ?? [];
  159. $slugResolver = $listingManager->getSerializedResolvedSlugs($section, $propertyType, (array) $request->query->get('locations', []));
  160. $faqData = $listingManager->getFaqs($section, $propertyType, $location, $request->getLocale());
  161. }
  162. $response = [
  163. 'locationChildren' => !empty($locationChildren) ? json_decode((string) $locationChildren, true) : [],
  164. 'longTail' => !empty($longTail) ? json_decode((string) $longTail, true) : [],
  165. 'customParagraph' => !empty($customParagraph) ? json_decode((string) $customParagraph, true) : [],
  166. 'slugResolver' => !empty($slugResolver) ? json_decode((string) $slugResolver, true) : [],
  167. 'faqData' => $faqData,
  168. 'locationParents' => $locationParents,
  169. 'sections' => json_decode((string) $sectionService->getSerializedSections(), true),
  170. 'propertyTypeChips' => $listingManager->getListingPropertyTypesChips($request, $location, $section, $propertyType),
  171. ];
  172. $cachedResponse->set(json_encode($response));
  173. $cachedResponse->expiresAfter(3600 * 3);
  174. $cache->save($cachedResponse);
  175. }
  176. return new JsonResponse(json_decode((string) $cachedResponse->get(), true));
  177. }
  178. #[Rest\Get('/api/v4/listings/trigger-search', options: ['i18n' => false], name: 'aqarmap_api_trigger_search_listings_v4')]
  179. public function triggerSearchListings(Request $request, MessageBusInterface $messageBus)
  180. {
  181. $user = $this->getUser();
  182. if ($request->get('location')) {
  183. $request->query->set('locations', explode(',', $request->query->get('location', '')));
  184. }
  185. if ($request->get('keywordSearch')) {
  186. $this->eventDispatcher->dispatch(new SearchTriggerEvent($request));
  187. }
  188. if ($user) {
  189. $messageBus->dispatch(new Search($request->query, $user));
  190. }
  191. return new JsonResponse([
  192. 'statusCode' => Response::HTTP_OK,
  193. 'statusMessage' => 'Search triggered successfully!',
  194. ]);
  195. }
  196. private function mapPaginatedBody(array $result, array $listings): array
  197. {
  198. if (!$result['pagination']) {
  199. return [
  200. 'statusCode' => $this->getStatusCode(),
  201. 'statusMessage' => $this->getStatusMessage(),
  202. 'paginate' => [],
  203. 'data' => [],
  204. 'errors' => $this->getErrors(),
  205. ];
  206. }
  207. return [
  208. 'statusCode' => $this->getStatusCode(),
  209. 'statusMessage' => $this->getStatusMessage(),
  210. 'paginate' => $result['pagination'],
  211. 'data' => $listings,
  212. 'errors' => $this->getErrors(),
  213. ];
  214. }
  215. }