Skip to content

Commit

Permalink
reimplement proper search (#30)
Browse files Browse the repository at this point in the history
  • Loading branch information
andreiio authored Aug 22, 2023
1 parent c5b08b1 commit 5330138
Show file tree
Hide file tree
Showing 31 changed files with 1,937 additions and 475 deletions.
73 changes: 0 additions & 73 deletions app/Concerns/InteractsWithSearch.php

This file was deleted.

63 changes: 63 additions & 0 deletions app/Concerns/Searchable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php

declare(strict_types=1);

namespace App\Concerns;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Str;
use Laravel\Scout\Searchable as ScoutSearchable;

trait Searchable
{
use ScoutSearchable {
search as scoutSearch;
}

/**
* Get the index name for the model.
*
* @return string
*/
public function searchableAs(): string
{
return config('scout.prefix') . app()->getLocale() . '_' . $this->getTable();
}

/**
* Perform a search against the model's indexed data.
*
* @param string $query
* @param \Closure $callback
* @return \Laravel\Scout\Builder
*/
public static function search($query = '', $callback = null)
{
$query = Str::of($query)
->lower()
->ascii()
->value();

return self::scoutSearch($query, $callback);
}

public static function searchAndFilter(?string $terms, ?array $filters)
{
if (filled($terms)) {
return self::search($terms)
->query(fn ($query) => $query->filter($filters));
}

return self::query()
->filter($filters);
}

public function scopeFilter(Builder $query, ?array $filters): Builder
{
return $query->filterQuery(
collect($filters)
->filter()
->all()
);
}
}
35 changes: 35 additions & 0 deletions app/Concerns/Translatable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

declare(strict_types=1);

namespace App\Concerns;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Str;
use Spatie\Translatable\HasTranslations;

trait Translatable
{
use HasTranslations;

public function scopeWhereTranslatable(Builder $query, string $column, string $value, string $boolean = 'and'): Builder
{
$locale = app()->getLocale();
$value = Str::lower($value);

if (Str::contains($column, '.')) {
$clause = $boolean === 'and' ? 'whereHas' : 'orWhereHas';

return $query->{$clause}($column, function ($query) use ($column, $value, $locale, $boolean) {
$query->whereRaw("LOWER({$column}->\"$.{$locale}\") LIKE ?", ["%{$value}%"], $boolean);
});
}

return $query->whereRaw("LOWER({$column}->\"$.{$locale}\") LIKE ?", ["%{$value}%"], $boolean);
}

public function scopeOrWhereTranslatable(Builder $query, string $column, string $value): Builder
{
return $query->whereTranslatable($column, $value, 'or');
}
}
54 changes: 54 additions & 0 deletions app/Console/Commands/RebuildSearchIndex.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

declare(strict_types=1);

namespace App\Console\Commands;

use App\Models\Ngo;
use App\Models\Service;
use Illuminate\Console\Command;
use Illuminate\Support\Traits\Localizable;

class RebuildSearchIndex extends Command
{
use Localizable;

/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'search:rebuild';

/**
* The console command description.
*
* @var string
*/
protected $description = 'Rebuilds the search index for all models';

protected $models = [
Ngo::class,
Service::class,
];

/**
* Execute the console command.
*/
public function handle()
{
app('languages')
->keys()
->crossJoin($this->models)
->each(function ($args) {
[$locale, $model] = $args;

$this->withLocale($locale, function () use ($model) {
$this->call('scout:flush', ['model' => $model]);
$this->call('scout:import', ['model' => $model]);
});
});

return self::SUCCESS;
}
}
33 changes: 33 additions & 0 deletions app/Helpers/Normalize.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

namespace App\Helpers;

use Illuminate\Support\Collection;
use Illuminate\Support\Str;

class Normalize
{
/**
* Strip tags and normalize the string to lowercase ascii.
*
* @param null|string $string
* @return string
*/
public static function string(?string $string): string
{
$string ??= '';

return Str::of(html_entity_decode($string))
->stripTags()
->lower()
->ascii()
->value();
}

public static function collection(Collection $input): Collection
{
return $input->map(fn (?string $value) => self::string($value));
}
}
83 changes: 57 additions & 26 deletions app/Http/Controllers/PageController.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,50 +12,81 @@ class PageController extends Controller
{
public function ngosPage(Request $request)
{
$ngos = Ngo::query()
->with('media')
->filter(collect($request->all()))->paginate(6);

return view('ngos', compact('ngos'));
$attributes = $request->validate([
'search' => ['nullable', 'string'],
'filter.county' => ['nullable', 'exists:counties,id'],
'filter.intervention_domain' => ['nullable', 'exists:intervention_domains,id'],
'filter.beneficiary' => ['nullable', 'exists:beneficiary_groups,id'],
]);

return view('ngos', [
'ngos' => Ngo::searchAndFilter(
data_get($attributes, 'search'),
data_get($attributes, 'filter'),
)->paginate(6),
]);
}

public function ngoPage(string $local, string $slug)
public function ngoPage(string $local, Ngo $ngo)
{
$ngo = Ngo::query()->where('slug', $slug)->with(['city', 'county'])->firstOrFail();



$breadcrumbs =
[
return view('ngos_index', [
'ngo' => $ngo,
'breadcrumbs' => [
[
'name' => __('txt.header.ngos'),
'url' => route('ngos', app()->getLocale()),
],
[
'name' => $ngo->name,
],

];

return view('ngos_index', compact('ngo', 'breadcrumbs'));
],
]);
}

public function services(Request $request)
{
$query = Service::query()->filter(collect($request->all()));
$servicesJson = $query->get();
$services = $query->paginate();

$attributes = $request->validate([
'search' => ['nullable', 'string'],
'filter.county' => ['nullable', 'exists:counties,id'],
'filter.intervention_domain' => ['nullable', 'exists:intervention_domains,id'],
'filter.beneficiary' => ['nullable', 'exists:beneficiary_groups,id'],
'filter.status' => ['nullable', 'string', 'in:active,finished'],
]);

return view('services', [
'view' => 'map',
'services' => Service::searchAndFilter(
data_get($attributes, 'search'),
data_get($attributes, 'filter'),
)->get(),
]);
}

return view('services', compact('services', 'servicesJson'));
public function servicesList(Request $request)
{
$attributes = $request->validate([
'search' => ['nullable', 'string'],
'filter.county' => ['nullable', 'exists:counties,id'],
'filter.intervention_domain' => ['nullable', 'exists:intervention_domains,id'],
'filter.beneficiary' => ['nullable', 'exists:beneficiary_groups,id'],
'filter.status' => ['nullable', 'string', 'in:active,finished'],
]);

return view('services', [
'view' => 'list',
'services' => Service::searchAndFilter(
data_get($attributes, 'search'),
data_get($attributes, 'filter'),
)->paginate(),
]);
}

public function home()
{
$totalServices = Service::count();
$totalNgos = Ngo::count();
$totalBeneficiaries = Ngo::sum('number_of_beneficiaries');

return view('home', compact('totalNgos', 'totalServices', 'totalBeneficiaries'));
return view('home', [
'totalServices' => Service::count(),
'totalNgos' => Ngo::count(),
'totalBeneficiaries' => Ngo::sum('number_of_beneficiaries'),
]);
}
}
4 changes: 2 additions & 2 deletions app/Models/ActivityDomain.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@
namespace App\Models;

use App\Concerns\ClearsResponseCache;
use App\Concerns\Translatable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Spatie\Translatable\HasTranslations;

class ActivityDomain extends Model
{
use ClearsResponseCache;
use HasFactory;
use HasTranslations;
use Translatable;

protected $fillable = [
'name',
Expand Down
Loading

0 comments on commit 5330138

Please sign in to comment.