Merge pull request #770 from jonnybarnes/develop

MTM Re-add search functionality
This commit is contained in:
Jonny Barnes 2023-04-12 10:19:14 +01:00 committed by GitHub
commit 2962675f9d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 298 additions and 20 deletions

View file

@ -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=

View file

@ -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

View 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'));
}
}

View file

@ -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
{ {

View file

@ -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
View file

@ -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
View 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'],
// ],
],
],
];

View file

@ -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>

View file

@ -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

View file

@ -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

View 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');
}
}