Merge pull request #770 from jonnybarnes/develop
MTM Re-add search functionality
This commit is contained in:
commit
2962675f9d
11 changed files with 298 additions and 20 deletions
|
@ -68,7 +68,8 @@ TWITTER_CONSUMER_SECRET=
|
||||||
TWITTER_ACCESS_TOKEN=
|
TWITTER_ACCESS_TOKEN=
|
||||||
TWITTER_ACCESS_TOKEN_SECRET=
|
TWITTER_ACCESS_TOKEN_SECRET=
|
||||||
|
|
||||||
SCOUT_DRIVER=pgsql
|
SCOUT_DRIVER=database
|
||||||
|
SCOUT_QUEUE=false
|
||||||
|
|
||||||
PIWIK=false
|
PIWIK=false
|
||||||
PIWIK_ID=1
|
PIWIK_ID=1
|
||||||
|
@ -79,7 +80,7 @@ FATHOM_ID=
|
||||||
APP_TIMEZONE=UTC
|
APP_TIMEZONE=UTC
|
||||||
APP_LANG=en
|
APP_LANG=en
|
||||||
APP_LOG=daily
|
APP_LOG=daily
|
||||||
SECURE_SESSION_COOKIE=true
|
SESSION_SECURE_COOKIE=true
|
||||||
|
|
||||||
LOG_SLACK_WEBHOOK_URL=
|
LOG_SLACK_WEBHOOK_URL=
|
||||||
|
|
||||||
|
|
|
@ -50,7 +50,8 @@ TWITTER_CONSUMER_SECRET=
|
||||||
TWITTER_ACCESS_TOKEN=
|
TWITTER_ACCESS_TOKEN=
|
||||||
TWITTER_ACCESS_TOKEN_SECRET=
|
TWITTER_ACCESS_TOKEN_SECRET=
|
||||||
|
|
||||||
SCOUT_DRIVER=pgsql
|
SCOUT_DRIVER=database
|
||||||
|
SCOUT_QUEUE=false
|
||||||
|
|
||||||
PIWIK=false
|
PIWIK=false
|
||||||
|
|
||||||
|
|
25
app/Http/Controllers/SearchController.php
Normal file
25
app/Http/Controllers/SearchController.php
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\Note;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
|
||||||
|
class SearchController extends Controller
|
||||||
|
{
|
||||||
|
public function search(Request $request): View
|
||||||
|
{
|
||||||
|
$search = $request->input('q');
|
||||||
|
|
||||||
|
$notes = Note::search($search)
|
||||||
|
->paginate();
|
||||||
|
|
||||||
|
/** @var Note $note */
|
||||||
|
foreach ($notes as $note) {
|
||||||
|
$note->load('place', 'media', 'client');
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('search', compact('search', 'notes'));
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,6 +19,7 @@ use Illuminate\Database\Eloquent\Relations\MorphMany;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
use Illuminate\Support\Facades\Cache;
|
use Illuminate\Support\Facades\Cache;
|
||||||
use Jonnybarnes\IndieWeb\Numbers;
|
use Jonnybarnes\IndieWeb\Numbers;
|
||||||
|
use Laravel\Scout\Searchable;
|
||||||
use League\CommonMark\Environment\Environment;
|
use League\CommonMark\Environment\Environment;
|
||||||
use League\CommonMark\Extension\Autolink\AutolinkExtension;
|
use League\CommonMark\Extension\Autolink\AutolinkExtension;
|
||||||
use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
|
use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
|
||||||
|
@ -34,6 +35,7 @@ use Spatie\CommonMarkHighlighter\IndentedCodeRenderer;
|
||||||
class Note extends Model
|
class Note extends Model
|
||||||
{
|
{
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
|
use Searchable;
|
||||||
use SoftDeletes;
|
use SoftDeletes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -96,7 +98,7 @@ class Note extends Model
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array<int, string>
|
* @return array<string, mixed>
|
||||||
*/
|
*/
|
||||||
public function toSearchableArray(): array
|
public function toSearchableArray(): array
|
||||||
{
|
{
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
"laravel/framework": "^10.0",
|
"laravel/framework": "^10.0",
|
||||||
"laravel/horizon": "^5.0",
|
"laravel/horizon": "^5.0",
|
||||||
"laravel/sanctum": "^3.0",
|
"laravel/sanctum": "^3.0",
|
||||||
|
"laravel/scout": "^10.1",
|
||||||
"laravel/tinker": "^2.0",
|
"laravel/tinker": "^2.0",
|
||||||
"lcobucci/jwt": "^5.0",
|
"lcobucci/jwt": "^5.0",
|
||||||
"league/commonmark": "^2.0",
|
"league/commonmark": "^2.0",
|
||||||
|
|
77
composer.lock
generated
77
composer.lock
generated
|
@ -4,7 +4,7 @@
|
||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "5e3ba7f9ad88d53aa871be437f02a0c5",
|
"content-hash": "617504ea1be00f742145197dbe3b5792",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "aws/aws-crt-php",
|
"name": "aws/aws-crt-php",
|
||||||
|
@ -2165,6 +2165,81 @@
|
||||||
},
|
},
|
||||||
"time": "2023-01-13T15:41:49+00:00"
|
"time": "2023-01-13T15:41:49+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "laravel/scout",
|
||||||
|
"version": "v10.1.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/laravel/scout.git",
|
||||||
|
"reference": "ee57968e7ec6943316f6cc66190baf4de8fb3cef"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/laravel/scout/zipball/ee57968e7ec6943316f6cc66190baf4de8fb3cef",
|
||||||
|
"reference": "ee57968e7ec6943316f6cc66190baf4de8fb3cef",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"illuminate/bus": "^9.0|^10.0",
|
||||||
|
"illuminate/contracts": "^9.0|^10.0",
|
||||||
|
"illuminate/database": "^9.0|^10.0",
|
||||||
|
"illuminate/http": "^9.0|^10.0",
|
||||||
|
"illuminate/pagination": "^9.0|^10.0",
|
||||||
|
"illuminate/queue": "^9.0|^10.0",
|
||||||
|
"illuminate/support": "^9.0|^10.0",
|
||||||
|
"php": "^8.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"algolia/algoliasearch-client-php": "^3.2",
|
||||||
|
"meilisearch/meilisearch-php": "^1.0",
|
||||||
|
"mockery/mockery": "^1.0",
|
||||||
|
"orchestra/testbench": "^7.0|^8.0",
|
||||||
|
"php-http/guzzle7-adapter": "^1.0",
|
||||||
|
"phpstan/phpstan": "^1.10",
|
||||||
|
"phpunit/phpunit": "^9.3"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"algolia/algoliasearch-client-php": "Required to use the Algolia engine (^3.2).",
|
||||||
|
"meilisearch/meilisearch-php": "Required to use the Meilisearch engine (^1.0)."
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "10.x-dev"
|
||||||
|
},
|
||||||
|
"laravel": {
|
||||||
|
"providers": [
|
||||||
|
"Laravel\\Scout\\ScoutServiceProvider"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Laravel\\Scout\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Taylor Otwell",
|
||||||
|
"email": "taylor@laravel.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Laravel Scout provides a driver based solution to searching your Eloquent models.",
|
||||||
|
"keywords": [
|
||||||
|
"algolia",
|
||||||
|
"laravel",
|
||||||
|
"search"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/laravel/scout/issues",
|
||||||
|
"source": "https://github.com/laravel/scout"
|
||||||
|
},
|
||||||
|
"time": "2023-04-11T16:38:05+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "laravel/serializable-closure",
|
"name": "laravel/serializable-closure",
|
||||||
"version": "v1.3.0",
|
"version": "v1.3.0",
|
||||||
|
|
142
config/scout.php
Normal file
142
config/scout.php
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Default Search Engine
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This option controls the default search connection that gets used while
|
||||||
|
| using Laravel Scout. This connection is used when syncing all models
|
||||||
|
| to the search service. You should adjust this based on your needs.
|
||||||
|
|
|
||||||
|
| Supported: "algolia", "meilisearch", "database", "collection", "null"
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'driver' => env('SCOUT_DRIVER', 'algolia'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Index Prefix
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here you may specify a prefix that will be applied to all search index
|
||||||
|
| names used by Scout. This prefix may be useful if you have multiple
|
||||||
|
| "tenants" or applications sharing the same search infrastructure.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'prefix' => env('SCOUT_PREFIX', ''),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Queue Data Syncing
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This option allows you to control if the operations that sync your data
|
||||||
|
| with your search engines are queued. When this is set to "true" then
|
||||||
|
| all automatic data syncing will get queued for better performance.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'queue' => env('SCOUT_QUEUE', false),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Database Transactions
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This configuration option determines if your data will only be synced
|
||||||
|
| with your search indexes after every open database transaction has
|
||||||
|
| been committed, thus preventing any discarded data from syncing.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'after_commit' => false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Chunk Sizes
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| These options allow you to control the maximum chunk size when you are
|
||||||
|
| mass importing data into the search engine. This allows you to fine
|
||||||
|
| tune each of these chunk sizes based on the power of the servers.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'chunk' => [
|
||||||
|
'searchable' => 500,
|
||||||
|
'unsearchable' => 500,
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Soft Deletes
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This option allows to control whether to keep soft deleted records in
|
||||||
|
| the search indexes. Maintaining soft deleted records can be useful
|
||||||
|
| if your application still needs to search for the records later.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'soft_delete' => false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Identify User
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This option allows you to control whether to notify the search engine
|
||||||
|
| of the user performing the search. This is sometimes useful if the
|
||||||
|
| engine supports any analytics based on this application's users.
|
||||||
|
|
|
||||||
|
| Supported engines: "algolia"
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'identify' => env('SCOUT_IDENTIFY', false),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Algolia Configuration
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here you may configure your Algolia settings. Algolia is a cloud hosted
|
||||||
|
| search engine which works great with Scout out of the box. Just plug
|
||||||
|
| in your application ID and admin API key to get started searching.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'algolia' => [
|
||||||
|
'id' => env('ALGOLIA_APP_ID', ''),
|
||||||
|
'secret' => env('ALGOLIA_SECRET', ''),
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Meilisearch Configuration
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here you may configure your Meilisearch settings. Meilisearch is an open
|
||||||
|
| source search engine with minimal configuration. Below, you can state
|
||||||
|
| the host and key information for your own Meilisearch installation.
|
||||||
|
|
|
||||||
|
| See: https://docs.meilisearch.com/guides/advanced_guides/configuration.html
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'meilisearch' => [
|
||||||
|
'host' => env('MEILISEARCH_HOST', 'http://localhost:7700'),
|
||||||
|
'key' => env('MEILISEARCH_KEY'),
|
||||||
|
'index-settings' => [
|
||||||
|
// 'users' => [
|
||||||
|
// 'filterableAttributes'=> ['id', 'name', 'email'],
|
||||||
|
// ],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
|
@ -51,9 +51,9 @@
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
{{-- <form action="search" method="get">--}}
|
<form action="/search" method="get">
|
||||||
{{-- <input type="text" name="terms" title="Search"><button type="submit">Search</button>--}}
|
<input type="text" name="q" title="Search"><button type="submit">Search</button>
|
||||||
{{-- </form>--}}
|
</form>
|
||||||
<p>Built with love: <a href="/colophon">Colophon</a></p>
|
<p>Built with love: <a href="/colophon">Colophon</a></p>
|
||||||
<a href="https://indieweb.org"><img src="/assets/img/iwc.svg" alt="Indie Web Camp logo" class="iwc-logo"></a>
|
<a href="https://indieweb.org"><img src="/assets/img/iwc.svg" alt="Indie Web Camp logo" class="iwc-logo"></a>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
|
@ -3,17 +3,18 @@
|
||||||
@section('title')Search « @stop
|
@section('title')Search « @stop
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<h2>Search Results</h2>
|
<h2>Search Results</h2>
|
||||||
@foreach($notes as $note)
|
<p>Searching for “{{ $search }}”</p>
|
||||||
<div class="h-entry">
|
|
||||||
@include('templates.note', ['note' => $note])
|
|
||||||
</div>
|
|
||||||
@endforeach
|
|
||||||
{{ $notes->links() }}
|
|
||||||
@stop
|
|
||||||
|
|
||||||
@section('scripts')
|
<div class="h-feed">
|
||||||
@include('templates.mapbox-links')
|
<!-- the following span stops microformat parses going haywire
|
||||||
<script src="/assets/js/links.js"></script>
|
generating a name property for the h-feed -->
|
||||||
<link rel="stylesheet" href="/assets/highlight/zenburn.css">
|
<span class="p-name"></span>
|
||||||
|
|
||||||
|
@foreach ($notes as $note)
|
||||||
|
@include('templates.note', ['note' => $note])
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ $notes->links() }}
|
||||||
@stop
|
@stop
|
||||||
|
|
|
@ -31,6 +31,7 @@ use App\Http\Controllers\MicropubController;
|
||||||
use App\Http\Controllers\MicropubMediaController;
|
use App\Http\Controllers\MicropubMediaController;
|
||||||
use App\Http\Controllers\NotesController;
|
use App\Http\Controllers\NotesController;
|
||||||
use App\Http\Controllers\PlacesController;
|
use App\Http\Controllers\PlacesController;
|
||||||
|
use App\Http\Controllers\SearchController;
|
||||||
use App\Http\Controllers\ShortURLsController;
|
use App\Http\Controllers\ShortURLsController;
|
||||||
use App\Http\Controllers\TokenEndpointController;
|
use App\Http\Controllers\TokenEndpointController;
|
||||||
use App\Http\Controllers\WebMentionsController;
|
use App\Http\Controllers\WebMentionsController;
|
||||||
|
@ -205,6 +206,9 @@ Route::group(['domain' => config('url.longurl')], function () {
|
||||||
|
|
||||||
// Micropub
|
// Micropub
|
||||||
Route::redirect('/micropub/create', '/notes/new');
|
Route::redirect('/micropub/create', '/notes/new');
|
||||||
|
|
||||||
|
// Search
|
||||||
|
Route::get('search', [SearchController::class, 'search']);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Short URL
|
// Short URL
|
||||||
|
|
26
tests/Feature/SearchTest.php
Normal file
26
tests/Feature/SearchTest.php
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Tests\Feature;
|
||||||
|
|
||||||
|
use App\Models\Note;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class SearchTest extends TestCase
|
||||||
|
{
|
||||||
|
/** @test */
|
||||||
|
public function searchEndpointReturnsResults(): void
|
||||||
|
{
|
||||||
|
Note::factory(10)->create();
|
||||||
|
Note::Factory()->create(['note' => 'hello world']);
|
||||||
|
|
||||||
|
$response = $this->get('/search?q=hello');
|
||||||
|
|
||||||
|
$response->assertStatus(200);
|
||||||
|
$response->assertViewIs('search');
|
||||||
|
$response->assertViewHas('search');
|
||||||
|
$response->assertViewHas('notes');
|
||||||
|
$response->assertSee('hello world');
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue