From b8235404b83bfd8a22e634a4224c2333543c7010 Mon Sep 17 00:00:00 2001 From: F4ilji Date: Fri, 18 Jul 2025 14:14:10 +0500 Subject: [PATCH] refactor search functionality; remove deprecated services and implement new action and task classes for improved structure and maintainability --- .gitignore | 3 +- .../Actions/ClearStaticSearchCacheAction.php | 22 ++ .../PerformCrossEloquentSearchAction.php | 64 +++++ .../RebuildStaticSearchIndexAction.php | 16 ++ .../Actions/SearchStaticFilesAction.php | 84 +++++++ .../Search/Services/CategoryFinderService.php | 36 --- .../Services/HtmlContentExtractorService.php | 20 -- .../Search/Services/StaticFileSearch.php | 218 ------------------ .../Search/Tasks/BuildStaticFileIndexTask.php | 87 +++++++ .../Search/Tasks/ExtractHtmlContentTask.php | 24 ++ .../FilterSearchResultsByCategoryTask.php | 17 ++ .../GetAvailableSearchCategoriesTask.php | 13 ++ .../GetBreadcrumbFromHtmlTask.php} | 19 +- .../Tasks/GetStaticFileCategoriesTask.php | 45 ++++ .../Tasks/GetStaticSearchCacheStatusTask.php | 22 ++ .../Search/Tasks/PaginateCollectionTask.php | 33 +++ .../Tasks/PaginateSearchResultsTask.php | 27 +++ .../SortAndHighlightSearchResultsTask.php | 42 ++++ .../UI/API/Controllers/SearchController.php | 163 ++----------- .../Controllers/StaticSearchController.php | 10 +- 20 files changed, 525 insertions(+), 440 deletions(-) create mode 100644 app/Containers/Search/Actions/ClearStaticSearchCacheAction.php create mode 100644 app/Containers/Search/Actions/PerformCrossEloquentSearchAction.php create mode 100644 app/Containers/Search/Actions/RebuildStaticSearchIndexAction.php create mode 100644 app/Containers/Search/Actions/SearchStaticFilesAction.php delete mode 100644 app/Containers/Search/Services/CategoryFinderService.php delete mode 100644 app/Containers/Search/Services/HtmlContentExtractorService.php delete mode 100644 app/Containers/Search/Services/StaticFileSearch.php create mode 100644 app/Containers/Search/Tasks/BuildStaticFileIndexTask.php create mode 100644 app/Containers/Search/Tasks/ExtractHtmlContentTask.php create mode 100644 app/Containers/Search/Tasks/FilterSearchResultsByCategoryTask.php create mode 100644 app/Containers/Search/Tasks/GetAvailableSearchCategoriesTask.php rename app/Containers/Search/{Services/BreadcrumbFinderService.php => Tasks/GetBreadcrumbFromHtmlTask.php} (54%) create mode 100644 app/Containers/Search/Tasks/GetStaticFileCategoriesTask.php create mode 100644 app/Containers/Search/Tasks/GetStaticSearchCacheStatusTask.php create mode 100644 app/Containers/Search/Tasks/PaginateCollectionTask.php create mode 100644 app/Containers/Search/Tasks/PaginateSearchResultsTask.php create mode 100644 app/Containers/Search/Tasks/SortAndHighlightSearchResultsTask.php diff --git a/.gitignore b/.gitignore index 613fd50..6f33216 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,5 @@ _deploy dump.sql .idea .DS_Store -**/.DS_Store +**/. +.cursor diff --git a/app/Containers/Search/Actions/ClearStaticSearchCacheAction.php b/app/Containers/Search/Actions/ClearStaticSearchCacheAction.php new file mode 100644 index 0000000..c699b14 --- /dev/null +++ b/app/Containers/Search/Actions/ClearStaticSearchCacheAction.php @@ -0,0 +1,22 @@ +getMessage()); + return false; + } + } +} diff --git a/app/Containers/Search/Actions/PerformCrossEloquentSearchAction.php b/app/Containers/Search/Actions/PerformCrossEloquentSearchAction.php new file mode 100644 index 0000000..b2bf2a5 --- /dev/null +++ b/app/Containers/Search/Actions/PerformCrossEloquentSearchAction.php @@ -0,0 +1,64 @@ + PostSearchResource::class, + Page::class => PageSearchResource::class, + EducationalGroup::class => EducationGroupSearchResource::class, + EducationalProgram::class => EducationalProgramSearchResource::class, + Event::class => EventSearchResource::class, + AdditionalEducation::class => AdditionalEducationSearchResource::class, //?? + User::class => UserSearchResource::class, //?? + Faculty::class => FacultySearchResource::class, //?? + ]; + + public function run(string $query): Collection + { + $results = Search::new() + ->add(Post::where('status', '=', 'published'), ['title', 'search_data'], 'publish_at') + ->add(Page::with('section')->where('searchable', '=', true), ['title', 'search_data']) + ->add(Event::where('event_date_start', '>', Date::now()), 'title', 'created_at') + ->add(AdditionalEducation::where('is_active', '=', true), 'title') + ->add(EducationalGroup::with('schedules'), 'title') + ->add(EducationalProgram::where('status', '=', true)->whereHas('admission_plans'), 'name') + ->add(Faculty::where('is_active', '=', true), 'title') + ->add(User::whereHas('userDetail'), 'name') + ->orderByDesc() + ->beginWithWildcard() + ->includeModelType() + ->ignoreCase(true) + ->search("$query"); + + $resourceMap = $this->resourceMap; + $resources = collect($results)->map(function ($result) use ($resourceMap) { + $resourceClass = $resourceMap[get_class($result)] ?? null; + return $resourceClass ? new $resourceClass($result) : null; + })->filter(); // Filter out nulls from resources + + return $resources; + } +} diff --git a/app/Containers/Search/Actions/RebuildStaticSearchIndexAction.php b/app/Containers/Search/Actions/RebuildStaticSearchIndexAction.php new file mode 100644 index 0000000..2797e78 --- /dev/null +++ b/app/Containers/Search/Actions/RebuildStaticSearchIndexAction.php @@ -0,0 +1,16 @@ +run(); + + return app(BuildStaticFileIndexTask::class)->run(); + } +} diff --git a/app/Containers/Search/Actions/SearchStaticFilesAction.php b/app/Containers/Search/Actions/SearchStaticFilesAction.php new file mode 100644 index 0000000..8e8a867 --- /dev/null +++ b/app/Containers/Search/Actions/SearchStaticFilesAction.php @@ -0,0 +1,84 @@ +input('search'); + $category = $request->input('category'); + $page = $request->input('page', 1); + + try { + $index = app(BuildStaticFileIndexTask::class)->run(); + $results = []; + $normalizedQuery = $this->normalizeText(Str::lower($query)); + + foreach ($index as $filePath => $content) { + if (stripos($content['content'], $normalizedQuery) !== false) { + $relativePath = str_replace(public_path() . '/', '', $filePath); + $results[] = [ + 'file' => $relativePath, + 'content' => $content['title'], + 'category' => trim($content['category']), + ]; + } + } + + $categories = array_values(array_unique(array_filter(array_column($results, 'category')))); + + if ($category !== null) { + $results = array_filter($results, function($item) use ($category) { + return $item['category'] === $category; + }); + + // Переиндексировать массив + $results = array_values($results); + } + + return app(PaginateSearchResultsTask::class)->run($results, $page, self::PER_PAGE, $categories); + } catch (\Exception $e) { + Log::error('Search error: ' . $e->getMessage()); + return [ + 'data' => [], + 'meta' => [ + 'current_page' => 1, + 'total' => 0, + 'per_page' => self::PER_PAGE, + 'last_page' => 1 + ] + ]; + } + } + + protected function normalizeText(string $text): string + { + $text = html_entity_decode($text, ENT_QUOTES | ENT_HTML5, 'UTF-8'); + $text = preg_replace('/\s+/u', ' ', $text); + $text = trim($text); + return Str::lower($text); + } + + protected function getFirstH1Content(string $content): ?string + { + try { + if (preg_match('/]*>(.*?)<\/h1>/is', $content, $matches)) { + return $this->normalizeText($matches[1]); + } + + return null; + } catch (\Exception $e) { + Log::error('Failed to get H1: ' . $e->getMessage()); + return null; + } + } +} diff --git a/app/Containers/Search/Services/CategoryFinderService.php b/app/Containers/Search/Services/CategoryFinderService.php deleted file mode 100644 index 73d962d..0000000 --- a/app/Containers/Search/Services/CategoryFinderService.php +++ /dev/null @@ -1,36 +0,0 @@ -first('ul.dropdown-menu'); - $links = $dropdownMenu->find('a'); - $categories = []; - - foreach ($links as $link) { - $category = trim($link->text()); - $categories[] = $category; - } - - return $categories; - } - - -} \ No newline at end of file diff --git a/app/Containers/Search/Services/HtmlContentExtractorService.php b/app/Containers/Search/Services/HtmlContentExtractorService.php deleted file mode 100644 index 4b72212..0000000 --- a/app/Containers/Search/Services/HtmlContentExtractorService.php +++ /dev/null @@ -1,20 +0,0 @@ -first('.vikon-content'); - $breadcrumb = $content->first('.row'); - $content->firstInDocument('.row')->remove(); - - return [$content->html(), $breadcrumb->html()] ?? null; - } - - -} \ No newline at end of file diff --git a/app/Containers/Search/Services/StaticFileSearch.php b/app/Containers/Search/Services/StaticFileSearch.php deleted file mode 100644 index 643f20b..0000000 --- a/app/Containers/Search/Services/StaticFileSearch.php +++ /dev/null @@ -1,218 +0,0 @@ -input('search'); - $category = $request->input('category'); - $page = request()->input('page', 1); - - - if ($query === null) { - return [ - 'data' => [] - ]; - } - - try { - $index = $this->getIndex(); - $results = []; - $normalizedQuery = $this->normalizeText(Str::lower($query)); - - foreach ($index as $filePath => $content) { - if (stripos($content['content'], $normalizedQuery) !== false) { - $relativePath = str_replace(public_path() . '/', '', $filePath); - $results[] = [ - 'file' => $relativePath, - 'content' => $content['title'], - 'category' => trim($content['category']), - ]; - } - } - - $categories = array_values(array_unique(array_filter(array_column($results, 'category')))); - - - if ($category !== null) { - $results = array_filter($results, function($item) use ($category) { - return $item['category'] === $category; - }); - - // Переиндексировать массив - $results = array_values($results); - } - - - return $this->paginateResults($results, $page, $categories); - } catch (\Exception $e) { - Log::error('Search error: ' . $e->getMessage()); - return [ - 'data' => [], - 'meta' => [ - 'current_page' => 1, - 'total' => 0, - 'per_page' => self::PER_PAGE, - 'last_page' => 1 - ] - ]; - } - } - - protected function paginateResults(array $results, int $page, ?array $categories): array - { - $total = count($results); - $lastPage = max(1, ceil($total / self::PER_PAGE)); - $page = max(1, min($page, $lastPage)); - - $offset = ($page - 1) * self::PER_PAGE; - $paginatedResults = array_slice($results, $offset, self::PER_PAGE); - - return [ - 'data' => $paginatedResults, - 'meta' => [ - 'current_page' => $page, - 'total' => $total, - 'per_page' => self::PER_PAGE, - 'last_page' => $lastPage - ], - 'categories' => $categories - ]; - } - - protected function getIndex(): array - { - return Cache::remember(self::CACHE_KEY, self::CACHE_TTL, function() { - try { - $index = []; - $directory = public_path(self::FILES_DIR); - - if (!is_dir($directory)) { - Log::error("Directory not found: {$directory}"); - return []; - } - - $iterator = new RecursiveIteratorIterator( - new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS), - RecursiveIteratorIterator::SELF_FIRST - ); - - foreach ($iterator as $file) { - if ($file->isFile() && $this->isHtmlFile($file)) { - $html = file_get_contents($file->getPathname()); - [$content, $breadcrumb] = app(HtmlContentExtractorService::class)->getContent($html); - if ($content !== false) { - $breadcrumb = app(BreadcrumbFinderService::class)->isSameCategory($breadcrumb); - $text = $this->normalizeText(strip_tags($content)); - $index[$file->getPathname()] = [ - 'title' => $this->getFirstH1Content($content), - 'content' => $text, - 'category' => $breadcrumb ?? null, - ]; - } - } - } - - return $index; - } catch (\Exception $e) { - Log::error('Index creation error: ' . $e->getMessage()); - return []; - } - }); - } - - protected function isHtmlFile(\SplFileInfo $file): bool - { - $extension = strtolower($file->getExtension()); - return in_array($extension, ['html', 'htm']); - } - - protected function normalizeText(string $text): string - { - $text = html_entity_decode($text, ENT_QUOTES | ENT_HTML5, 'UTF-8'); - $text = preg_replace('/\s+/u', ' ', $text); - $text = trim($text); - return Str::lower($text); - } - - protected function findMatches(string $content, string $query): array - { - $matches = []; - $offset = 0; - $query = Str::lower($query); - $content = Str::lower($content); - $queryLength = mb_strlen($query, 'UTF-8'); - - while (($offset = mb_strpos($content, $query, $offset, 'UTF-8')) !== false) { - $start = max(0, $offset - 50); - $length = min(150, mb_strlen($content) - $start); - $excerpt = mb_substr($content, $start, $length, 'UTF-8'); - - $matches[] = str_replace( - $query, - '[[HIGHLIGHT]]'.$query.'[[/HIGHLIGHT]]', - $excerpt - ); - - $offset += $queryLength; - } - - return $matches; - } - - public function clearCache(): bool - { - try { - Cache::forget(self::CACHE_KEY); - return true; - } catch (\Exception $e) { - Log::error('Cache clear error: ' . $e->getMessage()); - return false; - } - } - - public function rebuildIndex(): array - { - $this->clearCache(); - return $this->getIndex(); - } - - public function getCacheStatus(): array - { - return [ - 'exists' => Cache::has(self::CACHE_KEY), - 'ttl' => Cache::get(self::CACHE_KEY.'_ttl', null), - 'driver' => config('cache.default'), - 'path' => public_path(self::FILES_DIR), - 'directory_exists' => is_dir(public_path(self::FILES_DIR)) - ]; - } - - public function getFirstH1Content(string $content): ?string - { - try { - if (preg_match('/]*>(.*?)<\/h1>/is', $content, $matches)) { - return $this->normalizeText($matches[1]); - } - - return null; - } catch (\Exception $e) { - Log::error('Failed to get H1: ' . $e->getMessage()); - return null; - } - } -} \ No newline at end of file diff --git a/app/Containers/Search/Tasks/BuildStaticFileIndexTask.php b/app/Containers/Search/Tasks/BuildStaticFileIndexTask.php new file mode 100644 index 0000000..d8e7e05 --- /dev/null +++ b/app/Containers/Search/Tasks/BuildStaticFileIndexTask.php @@ -0,0 +1,87 @@ +isFile() && $this->isHtmlFile($file)) { + $html = file_get_contents($file->getPathname()); + [$content, $breadcrumb] = app(ExtractHtmlContentTask::class)->run($html); + if ($content !== false) { + $breadcrumb = app(GetBreadcrumbFromHtmlTask::class)->run($breadcrumb); + $text = $this->normalizeText(strip_tags($content)); + $index[$file->getPathname()] = [ + 'title' => $this->getFirstH1Content($content), + 'content' => $text, + 'category' => $breadcrumb ?? null, + ]; + } + } + } + + return $index; + } catch (\Exception $e) { + Log::error('Index creation error: ' . $e->getMessage()); + return []; + } + }); + } + + protected function isHtmlFile(\SplFileInfo $file): bool + { + $extension = strtolower($file->getExtension()); + return in_array($extension, ['html', 'htm']); + } + + protected function normalizeText(string $text): string + { + $text = html_entity_decode($text, ENT_QUOTES | ENT_HTML5, 'UTF-8'); + $text = preg_replace('/\s+/u', ' ', $text); + $text = trim($text); + return Str::lower($text); + } + + public function getFirstH1Content(string $content): ?string + { + try { + if (preg_match('/]*>(.*?)<\/h1>/is', $content, $matches)) { + return $this->normalizeText($matches[1]); + } + + return null; + } catch (\Exception $e) { + Log::error('Failed to get H1: ' . $e->getMessage()); + return null; + } + } +} diff --git a/app/Containers/Search/Tasks/ExtractHtmlContentTask.php b/app/Containers/Search/Tasks/ExtractHtmlContentTask.php new file mode 100644 index 0000000..ba77856 --- /dev/null +++ b/app/Containers/Search/Tasks/ExtractHtmlContentTask.php @@ -0,0 +1,24 @@ +first('.vikon-content'); + if ($content === null) { + return null; + } + + $breadcrumb = $content->first('.row'); + if ($breadcrumb !== null) { + $content->firstInDocument('.row')->remove(); + } + + return [$content->html(), $breadcrumb->html() ?? null]; + } +} diff --git a/app/Containers/Search/Tasks/FilterSearchResultsByCategoryTask.php b/app/Containers/Search/Tasks/FilterSearchResultsByCategoryTask.php new file mode 100644 index 0000000..d4f5702 --- /dev/null +++ b/app/Containers/Search/Tasks/FilterSearchResultsByCategoryTask.php @@ -0,0 +1,17 @@ +where('type', $category); + } + } +} diff --git a/app/Containers/Search/Tasks/GetAvailableSearchCategoriesTask.php b/app/Containers/Search/Tasks/GetAvailableSearchCategoriesTask.php new file mode 100644 index 0000000..55c3336 --- /dev/null +++ b/app/Containers/Search/Tasks/GetAvailableSearchCategoriesTask.php @@ -0,0 +1,13 @@ +pluck('type')->unique()->values()->all(); + } +} diff --git a/app/Containers/Search/Services/BreadcrumbFinderService.php b/app/Containers/Search/Tasks/GetBreadcrumbFromHtmlTask.php similarity index 54% rename from app/Containers/Search/Services/BreadcrumbFinderService.php rename to app/Containers/Search/Tasks/GetBreadcrumbFromHtmlTask.php index b03bec7..af8d72a 100644 --- a/app/Containers/Search/Services/BreadcrumbFinderService.php +++ b/app/Containers/Search/Tasks/GetBreadcrumbFromHtmlTask.php @@ -1,31 +1,22 @@ getBreadcrumb($html); - } - - private function getBreadcrumb(string $html): ?string + public function run(string $html): ?string { $document = new Document($html); $breadcrumbs = $document->first('ol.breadcrumb')?->find('li') ?? []; foreach ($breadcrumbs as $index => $breadcrumb) { if ($index === 2) { // Индексация с 0 → третий элемент = 2 - return $breadcrumb->text(); + return trim($breadcrumb->text()); } } return null; } - - -} \ No newline at end of file +} diff --git a/app/Containers/Search/Tasks/GetStaticFileCategoriesTask.php b/app/Containers/Search/Tasks/GetStaticFileCategoriesTask.php new file mode 100644 index 0000000..6524249 --- /dev/null +++ b/app/Containers/Search/Tasks/GetStaticFileCategoriesTask.php @@ -0,0 +1,45 @@ +first('ul.dropdown-menu'); + + if ($dropdownMenu === null) { + Log::warning("Dropdown menu not found in category index file: {$file}"); + return []; + } + + $links = $dropdownMenu->find('a'); + $categories = []; + + foreach ($links as $link) { + $category = trim($link->text()); + $categories[] = $category; + } + + return $categories; + } catch (\Exception $e) { + Log::error('Error extracting categories: ' . $e->getMessage()); + return []; + } + } +} diff --git a/app/Containers/Search/Tasks/GetStaticSearchCacheStatusTask.php b/app/Containers/Search/Tasks/GetStaticSearchCacheStatusTask.php new file mode 100644 index 0000000..995ee35 --- /dev/null +++ b/app/Containers/Search/Tasks/GetStaticSearchCacheStatusTask.php @@ -0,0 +1,22 @@ + Cache::has(self::CACHE_KEY), + 'ttl' => Cache::get(self::CACHE_KEY.'_ttl', null), + 'driver' => config('cache.default'), + 'path' => public_path(self::FILES_DIR), + 'directory_exists' => is_dir(public_path(self::FILES_DIR)) + ]; + } +} diff --git a/app/Containers/Search/Tasks/PaginateCollectionTask.php b/app/Containers/Search/Tasks/PaginateCollectionTask.php new file mode 100644 index 0000000..289110f --- /dev/null +++ b/app/Containers/Search/Tasks/PaginateCollectionTask.php @@ -0,0 +1,33 @@ +slice(($currentPage - 1) * $perPage, $perPage)->all(); + + // Создаем экземпляр LengthAwarePaginator + $paginator = new LengthAwarePaginator($currentItems, count($resources), $perPage, $currentPage, [ + 'path' => $request->url(), + 'query' => $request->query(), // Changed from $request->query to $request->query() + ]); + + $nextPage = $paginator->hasMorePages() ? $paginator->currentPage() + 1 : null; + $prevPage = $paginator->onFirstPage() ? null : $paginator->currentPage() - 1; + + return [ + 'paginator' => $paginator, + 'next_page' => $nextPage, + 'prev_page' => $prevPage + ]; + } +} diff --git a/app/Containers/Search/Tasks/PaginateSearchResultsTask.php b/app/Containers/Search/Tasks/PaginateSearchResultsTask.php new file mode 100644 index 0000000..1849012 --- /dev/null +++ b/app/Containers/Search/Tasks/PaginateSearchResultsTask.php @@ -0,0 +1,27 @@ + $paginatedResults, + 'meta' => [ + 'current_page' => $page, + 'total' => $total, + 'per_page' => $perPage, + 'last_page' => $lastPage + ], + 'categories' => $categories + ]; + } +} diff --git a/app/Containers/Search/Tasks/SortAndHighlightSearchResultsTask.php b/app/Containers/Search/Tasks/SortAndHighlightSearchResultsTask.php new file mode 100644 index 0000000..0938984 --- /dev/null +++ b/app/Containers/Search/Tasks/SortAndHighlightSearchResultsTask.php @@ -0,0 +1,42 @@ +getMatches($searchData, $searchRequest); + $sortedData[$item['type']][] = [ + 'data' => $item, + 'matches' => $matches, + 'tag' => $item['type'] + ]; + } + + return $sortedData; + } + + private function getMatches(string $haystack, string $needle): array + { + $matches = []; + $offset = 0; + if (($offset = mb_strpos($haystack, $needle, $offset, 'UTF-8')) !== false) { + for ($i = 0; 1 > count($matches); $i++) { + $left = max(0, $offset - 50); + $right = min(mb_strlen($haystack, 'UTF-8'), $offset + 100); + $excerpt = mb_substr($haystack, $left, $right - $left, 'UTF-8'); + $matches[] = $excerpt; + } + } + + return $matches; + } +} diff --git a/app/Containers/Search/UI/API/Controllers/SearchController.php b/app/Containers/Search/UI/API/Controllers/SearchController.php index 3a0188c..a49cb1f 100644 --- a/app/Containers/Search/UI/API/Controllers/SearchController.php +++ b/app/Containers/Search/UI/API/Controllers/SearchController.php @@ -2,175 +2,46 @@ namespace App\Containers\Search\UI\API\Controllers; -use App\Containers\AdditionalEducation\Models\AdditionalEducation; -use App\Containers\AppStructure\Models\Page; -use App\Containers\Article\Models\Post; -use App\Containers\Education\Models\EducationalProgram; -use App\Containers\Event\Models\Event; -use App\Containers\InstituteStructure\Models\Faculty; -use App\Containers\Schedule\Models\EducationalGroup; -use App\Containers\Search\UI\API\Transformers\AdditionalEducationSearchResource; -use App\Containers\Search\UI\API\Transformers\EducationalProgramSearchResource; -use App\Containers\Search\UI\API\Transformers\EducationGroupSearchResource; -use App\Containers\Search\UI\API\Transformers\EventSearchResource; -use App\Containers\Search\UI\API\Transformers\FacultySearchResource; -use App\Containers\Search\UI\API\Transformers\PageSearchResource; -use App\Containers\Search\UI\API\Transformers\PostSearchResource; -use App\Containers\Search\UI\API\Transformers\UserSearchResource; -use App\Containers\User\Models\User; +use App\Containers\Search\Actions\PerformCrossEloquentSearchAction; +use App\Containers\Search\Tasks\FilterSearchResultsByCategoryTask; +use App\Containers\Search\Tasks\GetAvailableSearchCategoriesTask; +use App\Containers\Search\Tasks\PaginateCollectionTask; +use App\Containers\Search\Tasks\SortAndHighlightSearchResultsTask; use App\Ship\Controllers\Controller; use Illuminate\Http\Request; -use Illuminate\Pagination\LengthAwarePaginator; -use Illuminate\Support\Collection; -use Illuminate\Support\Facades\Date; use Illuminate\Support\Str; -use ProtoneMedia\LaravelCrossEloquentSearch\Search; class SearchController extends Controller { - private array $resourceMap = [ - Post::class => PostSearchResource::class, - Page::class => PageSearchResource::class, - EducationalGroup::class => EducationGroupSearchResource::class, - EducationalProgram::class => EducationalProgramSearchResource::class, - Event::class => EventSearchResource::class, - AdditionalEducation::class => AdditionalEducationSearchResource::class, //?? - User::class => UserSearchResource::class, //?? - Faculty::class => FacultySearchResource::class, //?? - ]; public function index(Request $request) { - $req = Str::lower($request->query('search')); - if (!$req) { + $searchQuery = Str::lower($request->query('search')); + if (!$searchQuery) { return response()->json([ 'searchRes' => null, ]); } + $allResources = app(PerformCrossEloquentSearchAction::class)->run($searchQuery); + $result_type = app(GetAvailableSearchCategoriesTask::class)->run($allResources); - $results = Search::new() - ->add(Post::where('status', '=', 'published'), ['title', 'search_data'], 'publish_at') - ->add(Page::with('section')->where('searchable', '=', true), ['title', 'search_data']) - ->add(Event::where('event_date_start', '>', Date::now()), 'title', 'created_at') - ->add(AdditionalEducation::where('is_active', '=', true), 'title') - ->add(EducationalGroup::with('schedules'), 'title') - ->add(EducationalProgram::where('status', '=', true)->whereHas('admission_plans'), 'name') - ->add(Faculty::where('is_active', '=', true), 'title') - ->add(User::whereHas('userDetail'), 'name') - ->orderByDesc() - ->beginWithWildcard() -// ->orderByRelevance() - ->includeModelType() - ->ignoreCase(true) - ->search("$req"); + $filteredResources = app(FilterSearchResultsByCategoryTask::class)->run($allResources, $request->query('category')); + $paginateData = app(PaginateCollectionTask::class)->run($filteredResources, $request, 7); - $resourceMap = $this->resourceMap; - $resources = collect($results)->map(function ($result) use ($resourceMap) { - $resourceClass = $resourceMap[get_class($result)] ?? null; - return $resourceClass ? new $resourceClass($result) : null; - }); - - $result_type = $this->getCategoriesSearchResult($resources); - - - if ($request->query('category')) { - $resources = $this->sortResourcesByCategory($resources, $request->query('category')); - } - - $paginate_data = $this->createPaginate($resources, $request, 7); - - $sortedData = $this->sortByType($paginate_data['paginator'], $req); + $sortedData = app(SortAndHighlightSearchResultsTask::class)->run($paginateData['paginator']->getCollection(), $searchQuery); return response()->json([ 'searchRes' => $sortedData, 'result_type' => $result_type, 'selectedCategory' => ($request->query('category') !== null) ? $request->query('category') : null, 'paginate' => [ - 'current_page' => $paginate_data['paginator']->currentPage(), - 'last_page' => $paginate_data['paginator']->lastPage(), - 'total' => $paginate_data['paginator']->total(), - 'next_page' => $paginate_data['next_page'], - 'prev_page' => $paginate_data['prev_page'], + 'current_page' => $paginateData['paginator']->currentPage(), + 'last_page' => $paginateData['paginator']->lastPage(), + 'total' => $paginateData['paginator']->total(), + 'next_page' => $paginateData['next_page'], + 'prev_page' => $paginateData['prev_page'], ] ]); } - private function sortByType(object $data, string $searchRequest): array - { - $sortedData = []; - - foreach ($data as $item) { - $searchData = $item['search_data'] ?? ''; - $matches = $this->getMatches($searchData, $searchRequest); - $sortedData[$item['type']][] = [ - 'data' => $item, - 'matches' => $matches, - 'tag' => $item['type'] - ]; - } - - return $sortedData; - } - - private function getMatches(string $haystack, string $needle): array - { - $matches = []; -// while (($offset = mb_strpos($haystack, $needle, $offset, 'UTF-8')) !== false) { -// $left = max(0, $offset - 50); -// $right = min(mb_strlen($haystack, 'UTF-8'), $offset + 100); -// $excerpt = mb_substr($haystack, $left, $right - $left, 'UTF-8'); -// $matches[] = $excerpt; -// $offset += mb_strlen($needle, 'UTF-8'); -// } - - $offset = 0; - if (($offset = mb_strpos($haystack, $needle, $offset, 'UTF-8')) !== false) { - for ($i = 0; 1 > count($matches); $i++) { - $left = max(0, $offset - 50); - $right = min(mb_strlen($haystack, 'UTF-8'), $offset + 100); - $excerpt = mb_substr($haystack, $left, $right - $left, 'UTF-8'); - $matches[] = $excerpt; - } - } - - return $matches; - } - - private function sortResourcesByCategory(Collection $resources, string $category) : Collection - { - if ($category === "All") { - $data = $resources; - } else { - $data = $resources->where('type', $category); - } - return $data; - } - - private function getCategoriesSearchResult(Collection $resources) - { - return $resources->pluck('type')->unique()->values()->all(); - } - - private function createPaginate($resources, $request, $perPage = 10) : array - { - $currentPage = LengthAwarePaginator::resolveCurrentPage(); - - // Отрезаем нужные элементы для текущей страницы - $currentItems = $resources->slice(($currentPage - 1) * $perPage, $perPage)->all(); - - // Создаем экземпляр LengthAwarePaginator - $paginator = new LengthAwarePaginator($currentItems, count($resources), $perPage, $currentPage, [ - 'path' => $request->url(), - 'query' => $request->query, - ]); - - $nextPage = $paginator->hasMorePages() ? $paginator->currentPage() + 1 : null; - $prevPage = $paginator->onFirstPage() ? null : $paginator->currentPage() - 1; - - return [ - 'paginator' => $paginator, - 'next_page' => $nextPage, - 'prev_page' => $prevPage - ]; - } } diff --git a/app/Containers/Search/UI/API/Controllers/StaticSearchController.php b/app/Containers/Search/UI/API/Controllers/StaticSearchController.php index b712e87..9827cec 100644 --- a/app/Containers/Search/UI/API/Controllers/StaticSearchController.php +++ b/app/Containers/Search/UI/API/Controllers/StaticSearchController.php @@ -2,8 +2,8 @@ namespace App\Containers\Search\UI\API\Controllers; -use App\Containers\Search\Services\CategoryFinderService; -use App\Containers\Search\Services\StaticFileSearch; +use App\Containers\Search\Actions\SearchStaticFilesAction; +use App\Containers\Search\Tasks\GetStaticFileCategoriesTask; use App\Ship\Controllers\Controller; use Illuminate\Http\Request; use Illuminate\Support\Facades\Cache; @@ -12,14 +12,14 @@ class StaticSearchController extends Controller { public function search(Request $request) { - return app(StaticFileSearch::class) - ->search($request); + return app(SearchStaticFilesAction::class) + ->run($request); } public function getCategories() { return Cache::remember('page_static_categories', now()->addWeek(), function () { - return app(CategoryFinderService::class)->getCategories(); + return app(GetStaticFileCategoriesTask::class)->run(); }); } }