feat: add Excerpt search

This commit is contained in:
Phan An 2020-12-24 13:41:18 +01:00
parent dbb91d24f9
commit 201da1caa5
17 changed files with 542 additions and 75 deletions

View file

@ -4,7 +4,6 @@ namespace App\Console\Commands;
use App\Models\Album;
use App\Models\Artist;
use App\Models\Playlist;
use App\Models\Song;
use Illuminate\Console\Command;
@ -14,7 +13,6 @@ class ImportSearchableEntitiesCommand extends Command
Song::class,
Album::class,
Artist::class,
Playlist::class,
];
protected $signature = 'koel:search:import';

View file

@ -3,22 +3,16 @@
namespace App\Http\Controllers\API\Search;
use App\Http\Controllers\API\Controller;
use App\Models\User;
use App\Services\SearchService;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Http\Request;
class ExcerptSearchController extends Controller
{
private $searchService;
/** @var User */
private $currentUser;
public function __construct(SearchService $searchService, ?Authenticatable $currentUser)
public function __construct(SearchService $searchService)
{
$this->searchService = $searchService;
$this->currentUser = $currentUser;
}
public function index(Request $request)
@ -28,7 +22,7 @@ class ExcerptSearchController extends Controller
}
return [
'results' => $this->searchService->excerptSearch($request->get('q'), $this->currentUser),
'results' => $this->searchService->excerptSearch($request->get('q')),
];
}
}

View file

@ -1,32 +0,0 @@
<?php
namespace App\Http\Controllers\API;
use App\Repositories\AlbumRepository;
use App\Repositories\ArtistRepository;
use App\Repositories\PlaylistRepository;
use App\Repositories\SongRepository;
class SearchController extends Controller
{
private $songRepository;
private $albumRepository;
private $artistRepository;
private $playlistRepository;
public function __construct(
SongRepository $songRepository,
AlbumRepository $albumRepository,
ArtistRepository $artistRepository,
PlaylistRepository $playlistRepository
) {
$this->songRepository = $songRepository;
$this->albumRepository = $albumRepository;
$this->artistRepository = $artistRepository;
$this->playlistRepository = $playlistRepository;
}
public function index()
{
}
}

View file

@ -9,7 +9,6 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Laravel\Scout\Searchable;
/**
* @property int $user_id
@ -26,7 +25,6 @@ class Playlist extends Model
{
use CanFilterByUser;
use HasFactory;
use Searchable;
protected $hidden = ['user_id', 'created_at', 'updated_at'];
protected $guarded = ['id'];
@ -50,13 +48,4 @@ class Playlist extends Model
{
return (bool) $this->rules;
}
/** @return array<mixed> */
public function toSearchableArray(): array
{
return [
'id' => $this->id,
'name' => $this->name,
];
}
}

View file

@ -255,10 +255,6 @@ class Song extends Model
$array['artist'] = $this->artist->name;
}
if (!$this->album->is_unknown) {
$array['album'] = $this->album->name;
}
return $array;
}

View file

@ -57,4 +57,9 @@ abstract class AbstractRepository implements RepositoryInterface
{
return $this->model->where(...$params)->first();
}
public function getModelClass(): string
{
return $this->modelClass;
}
}

View file

@ -3,9 +3,12 @@
namespace App\Repositories;
use App\Models\Song;
use App\Repositories\Traits\Searchable;
class ArtistRepository extends AbstractRepository
{
use Searchable;
/** @return array<int> */
public function getNonEmptyArtistIds(): array
{

View file

@ -4,13 +4,11 @@ namespace App\Repositories;
use App\Models\Playlist;
use App\Repositories\Traits\ByCurrentUser;
use App\Repositories\Traits\Searchable;
use Illuminate\Support\Collection;
class PlaylistRepository extends AbstractRepository
{
use ByCurrentUser;
use Searchable;
/** @return Collection|array<Playlist> */
public function getAllByCurrentUser(): Collection

View file

@ -2,7 +2,12 @@
namespace App\Repositories\Traits;
use Laravel\Scout\Builder;
trait Searchable
{
public function search(string $keywords): Builder
{
return forward_static_call([$this->getModelClass(), 'search'], $keywords);
}
}

View file

@ -221,7 +221,7 @@ class FileSynchronizer
// If the album has no cover, we try to get the cover image from existing tag data
if ($coverData) {
$extension = explode('/', $coverData['image_mime']);
$extension = $extension[1] ? 'png' : $extension[1];
$extension = $extension[1] ?? 'png';
$this->mediaMetadataService->writeAlbumCover($album, $coverData['data'], $extension);

View file

@ -0,0 +1,43 @@
<?php
namespace App\Services;
use App\Repositories\AlbumRepository;
use App\Repositories\ArtistRepository;
use App\Repositories\SongRepository;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Builder;
class SearchService
{
private $songRepository;
private $albumRepository;
private $artistRepository;
public function __construct(
SongRepository $songRepository,
AlbumRepository $albumRepository,
ArtistRepository $artistRepository
) {
$this->songRepository = $songRepository;
$this->albumRepository = $albumRepository;
$this->artistRepository = $artistRepository;
}
/** @return array<mixed> */
public function excerptSearch(string $keywords): array
{
return [
'songs' => self::getTopResults($this->songRepository->search($keywords)),
'artists' => self::getTopResults($this->artistRepository->search($keywords)),
'albums' => self::getTopResults($this->albumRepository->search($keywords)),
];
}
/** @return Collection|array<Model> */
private static function getTopResults(Builder $query, int $count = 6): Collection
{
return $query->take($count)->get();
}
}

View file

@ -30,6 +30,7 @@
"teamtnt/laravel-scout-tntsearch-driver": "^11.1"
},
"require-dev": {
"facade/ignition": "^2.5",
"mockery/mockery": "~1.0",
"phpunit/phpunit": "^9.0",
"laravel/tinker": "^2.0",

284
composer.lock generated
View file

@ -4,20 +4,20 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "e6435a1c6c0bb1c2e691c892936a2366",
"content-hash": "7b7d97f1f8c3818ebfed25d4e6ccf591",
"packages": [
{
"name": "aws/aws-sdk-php",
"version": "3.171.3",
"version": "3.171.5",
"source": {
"type": "git",
"url": "https://github.com/aws/aws-sdk-php.git",
"reference": "7469c8e7f8db5dec544937677d08d8fff4f2f4e9"
"reference": "848923bc9ce5f8c5ede2df7175b090e382bba699"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/7469c8e7f8db5dec544937677d08d8fff4f2f4e9",
"reference": "7469c8e7f8db5dec544937677d08d8fff4f2f4e9",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/848923bc9ce5f8c5ede2df7175b090e382bba699",
"reference": "848923bc9ce5f8c5ede2df7175b090e382bba699",
"shasum": ""
},
"require": {
@ -92,9 +92,9 @@
"support": {
"forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
"issues": "https://github.com/aws/aws-sdk-php/issues",
"source": "https://github.com/aws/aws-sdk-php/tree/3.171.3"
"source": "https://github.com/aws/aws-sdk-php/tree/3.171.5"
},
"time": "2020-12-21T19:14:02+00:00"
"time": "2020-12-22T21:56:27+00:00"
},
{
"name": "aws/aws-sdk-php-laravel",
@ -1379,16 +1379,16 @@
},
{
"name": "laravel/framework",
"version": "v8.20.0",
"version": "v8.20.1",
"source": {
"type": "git",
"url": "https://github.com/laravel/framework.git",
"reference": "130168c9dcd399f6b42bccfe4882b88c81a4edfe"
"reference": "b5d8573ab16027867eaa1ac148893833434f9b02"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/framework/zipball/130168c9dcd399f6b42bccfe4882b88c81a4edfe",
"reference": "130168c9dcd399f6b42bccfe4882b88c81a4edfe",
"url": "https://api.github.com/repos/laravel/framework/zipball/b5d8573ab16027867eaa1ac148893833434f9b02",
"reference": "b5d8573ab16027867eaa1ac148893833434f9b02",
"shasum": ""
},
"require": {
@ -1542,7 +1542,7 @@
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
},
"time": "2020-12-22T17:01:04+00:00"
"time": "2020-12-22T21:21:19+00:00"
},
{
"name": "laravel/helpers",
@ -6577,6 +6577,201 @@
],
"time": "2020-11-10T18:47:58+00:00"
},
{
"name": "facade/flare-client-php",
"version": "1.3.7",
"source": {
"type": "git",
"url": "https://github.com/facade/flare-client-php.git",
"reference": "fd688d3c06658f2b3b5f7bb19f051ee4ddf02492"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/facade/flare-client-php/zipball/fd688d3c06658f2b3b5f7bb19f051ee4ddf02492",
"reference": "fd688d3c06658f2b3b5f7bb19f051ee4ddf02492",
"shasum": ""
},
"require": {
"facade/ignition-contracts": "~1.0",
"illuminate/pipeline": "^5.5|^6.0|^7.0|^8.0",
"php": "^7.1|^8.0",
"symfony/http-foundation": "^3.3|^4.1|^5.0",
"symfony/mime": "^3.4|^4.0|^5.1",
"symfony/var-dumper": "^3.4|^4.0|^5.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.14",
"phpunit/phpunit": "^7.5.16",
"spatie/phpunit-snapshot-assertions": "^2.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
}
},
"autoload": {
"psr-4": {
"Facade\\FlareClient\\": "src"
},
"files": [
"src/helpers.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "Send PHP errors to Flare",
"homepage": "https://github.com/facade/flare-client-php",
"keywords": [
"exception",
"facade",
"flare",
"reporting"
],
"support": {
"issues": "https://github.com/facade/flare-client-php/issues",
"source": "https://github.com/facade/flare-client-php/tree/1.3.7"
},
"funding": [
{
"url": "https://github.com/spatie",
"type": "github"
}
],
"time": "2020-10-21T16:02:39+00:00"
},
{
"name": "facade/ignition",
"version": "2.5.3",
"source": {
"type": "git",
"url": "https://github.com/facade/ignition.git",
"reference": "d8dc4f90ed469f9f9313b976fb078c20585d5c99"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/facade/ignition/zipball/d8dc4f90ed469f9f9313b976fb078c20585d5c99",
"reference": "d8dc4f90ed469f9f9313b976fb078c20585d5c99",
"shasum": ""
},
"require": {
"ext-json": "*",
"ext-mbstring": "*",
"facade/flare-client-php": "^1.3.7",
"facade/ignition-contracts": "^1.0.2",
"filp/whoops": "^2.4",
"illuminate/support": "^7.0|^8.0",
"monolog/monolog": "^2.0",
"php": "^7.2.5|^8.0",
"symfony/console": "^5.0",
"symfony/var-dumper": "^5.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.14",
"mockery/mockery": "^1.3",
"orchestra/testbench": "^5.0|^6.0",
"psalm/plugin-laravel": "^1.2"
},
"suggest": {
"laravel/telescope": "^3.1"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.x-dev"
},
"laravel": {
"providers": [
"Facade\\Ignition\\IgnitionServiceProvider"
],
"aliases": {
"Flare": "Facade\\Ignition\\Facades\\Flare"
}
}
},
"autoload": {
"psr-4": {
"Facade\\Ignition\\": "src"
},
"files": [
"src/helpers.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "A beautiful error page for Laravel applications.",
"homepage": "https://github.com/facade/ignition",
"keywords": [
"error",
"flare",
"laravel",
"page"
],
"support": {
"docs": "https://flareapp.io/docs/ignition-for-laravel/introduction",
"forum": "https://twitter.com/flareappio",
"issues": "https://github.com/facade/ignition/issues",
"source": "https://github.com/facade/ignition"
},
"time": "2020-12-09T20:25:45+00:00"
},
{
"name": "facade/ignition-contracts",
"version": "1.0.2",
"source": {
"type": "git",
"url": "https://github.com/facade/ignition-contracts.git",
"reference": "3c921a1cdba35b68a7f0ccffc6dffc1995b18267"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/facade/ignition-contracts/zipball/3c921a1cdba35b68a7f0ccffc6dffc1995b18267",
"reference": "3c921a1cdba35b68a7f0ccffc6dffc1995b18267",
"shasum": ""
},
"require": {
"php": "^7.3|^8.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^v2.15.8",
"phpunit/phpunit": "^9.3.11",
"vimeo/psalm": "^3.17.1"
},
"type": "library",
"autoload": {
"psr-4": {
"Facade\\IgnitionContracts\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Freek Van der Herten",
"email": "freek@spatie.be",
"homepage": "https://flareapp.io",
"role": "Developer"
}
],
"description": "Solution contracts for Ignition",
"homepage": "https://github.com/facade/ignition-contracts",
"keywords": [
"contracts",
"flare",
"ignition"
],
"support": {
"issues": "https://github.com/facade/ignition-contracts/issues",
"source": "https://github.com/facade/ignition-contracts/tree/1.0.2"
},
"time": "2020-10-16T08:27:54+00:00"
},
{
"name": "fakerphp/faker",
"version": "v1.13.0",
@ -6629,6 +6824,71 @@
},
"time": "2020-12-18T16:50:48+00:00"
},
{
"name": "filp/whoops",
"version": "2.9.1",
"source": {
"type": "git",
"url": "https://github.com/filp/whoops.git",
"reference": "307fb34a5ab697461ec4c9db865b20ff2fd40771"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/filp/whoops/zipball/307fb34a5ab697461ec4c9db865b20ff2fd40771",
"reference": "307fb34a5ab697461ec4c9db865b20ff2fd40771",
"shasum": ""
},
"require": {
"php": "^5.5.9 || ^7.0 || ^8.0",
"psr/log": "^1.0.1"
},
"require-dev": {
"mockery/mockery": "^0.9 || ^1.0",
"phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3",
"symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0"
},
"suggest": {
"symfony/var-dumper": "Pretty print complex values better with var-dumper available",
"whoops/soap": "Formats errors as SOAP responses"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.7-dev"
}
},
"autoload": {
"psr-4": {
"Whoops\\": "src/Whoops/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Filipe Dobreira",
"homepage": "https://github.com/filp",
"role": "Developer"
}
],
"description": "php error handling for cool kids",
"homepage": "https://filp.github.io/whoops/",
"keywords": [
"error",
"exception",
"handling",
"library",
"throwable",
"whoops"
],
"support": {
"issues": "https://github.com/filp/whoops/issues",
"source": "https://github.com/filp/whoops/tree/2.9.1"
},
"time": "2020-11-01T12:00:00+00:00"
},
{
"name": "hamcrest/hamcrest-php",
"version": "v2.0.1",

127
config/ignition.php Normal file
View file

@ -0,0 +1,127 @@
<?php
use Facade\Ignition\SolutionProviders\MissingPackageSolutionProvider;
return [
/*
|--------------------------------------------------------------------------
| Editor
|--------------------------------------------------------------------------
|
| Choose your preferred editor to use when clicking any edit button.
|
| Supported: "phpstorm", "vscode", "vscode-insiders",
| "sublime", "atom"
|
*/
'editor' => env('IGNITION_EDITOR', 'phpstorm'),
/*
|--------------------------------------------------------------------------
| Theme
|--------------------------------------------------------------------------
|
| Here you may specify which theme Ignition should use.
|
| Supported: "light", "dark", "auto"
|
*/
'theme' => env('IGNITION_THEME', 'dark'),
/*
|--------------------------------------------------------------------------
| Sharing
|--------------------------------------------------------------------------
|
| You can share local errors with colleagues or others around the world.
| Sharing is completely free and doesn't require an account on Flare.
|
| If necessary, you can completely disable sharing below.
|
*/
'enable_share_button' => env('IGNITION_SHARING_ENABLED', false),
/*
|--------------------------------------------------------------------------
| Register Ignition commands
|--------------------------------------------------------------------------
|
| Ignition comes with an additional make command that lets you create
| new solution classes more easily. To keep your default Laravel
| installation clean, this command is not registered by default.
|
| You can enable the command registration below.
|
*/
'register_commands' => env('REGISTER_IGNITION_COMMANDS', false),
/*
|--------------------------------------------------------------------------
| Ignored Solution Providers
|--------------------------------------------------------------------------
|
| You may specify a list of solution providers (as fully qualified class
| names) that shouldn't be loaded. Ignition will ignore these classes
| and possible solutions provided by them will never be displayed.
|
*/
'ignored_solution_providers' => [
MissingPackageSolutionProvider::class,
],
/*
|--------------------------------------------------------------------------
| Runnable Solutions
|--------------------------------------------------------------------------
|
| Some solutions that Ignition displays are runnable and can perform
| various tasks. Runnable solutions are enabled when your app has
| debug mode enabled. You may also fully disable this feature.
|
*/
'enable_runnable_solutions' => env('IGNITION_ENABLE_RUNNABLE_SOLUTIONS', null),
/*
|--------------------------------------------------------------------------
| Remote Path Mapping
|--------------------------------------------------------------------------
|
| If you are using a remote dev server, like Laravel Homestead, Docker, or
| even a remote VPS, it will be necessary to specify your path mapping.
|
| Leaving one, or both of these, empty or null will not trigger the remote
| URL changes and Ignition will treat your editor links as local files.
|
| "remote_sites_path" is an absolute base path for your sites or projects
| in Homestead, Vagrant, Docker, or another remote development server.
|
| Example value: "/home/vagrant/Code"
|
| "local_sites_path" is an absolute base path for your sites or projects
| on your local computer where your IDE or code editor is running on.
|
| Example values: "/Users/<name>/Code", "C:\Users\<name>\Documents\Code"
|
*/
'remote_sites_path' => env('IGNITION_REMOTE_SITES_PATH', ''),
'local_sites_path' => env('IGNITION_LOCAL_SITES_PATH', ''),
/*
|--------------------------------------------------------------------------
| Housekeeping Endpoint Prefix
|--------------------------------------------------------------------------
|
| Ignition registers a couple of routes when it is enabled. Below you may
| specify a route prefix that will be used to host all internal links.
|
*/
'housekeeping_endpoint_prefix' => '_ignition',
];

1
public/.gitignore vendored
View file

@ -1,6 +1,7 @@
css
fonts
img
images
js
manifest.json
manifest-remote.json

View file

@ -3,6 +3,81 @@
"/css/app.css": "/css/app.css",
"/css/remote.css": "/css/remote.css",
"/js/remote/app.js": "/js/remote/app.js",
"/js/app.07a760363b90b9ec000a.hot-update.js": "/js/app.07a760363b90b9ec000a.hot-update.js",
"/js/remote/app.07a760363b90b9ec000a.hot-update.js": "/js/remote/app.07a760363b90b9ec000a.hot-update.js"
"/js/app.1cbd8737109474c039c9.hot-update.js": "/js/app.1cbd8737109474c039c9.hot-update.js",
"/js/app.3c527da504a4ebc4827f.hot-update.js": "/js/app.3c527da504a4ebc4827f.hot-update.js",
"/js/remote/app.3c527da504a4ebc4827f.hot-update.js": "/js/remote/app.3c527da504a4ebc4827f.hot-update.js",
"/js/app.b6c93b922be9bb8c18b1.hot-update.js": "/js/app.b6c93b922be9bb8c18b1.hot-update.js",
"/js/remote/app.b6c93b922be9bb8c18b1.hot-update.js": "/js/remote/app.b6c93b922be9bb8c18b1.hot-update.js",
"/js/app.217fd5c30f6e003ad3b5.hot-update.js": "/js/app.217fd5c30f6e003ad3b5.hot-update.js",
"/js/remote/app.217fd5c30f6e003ad3b5.hot-update.js": "/js/remote/app.217fd5c30f6e003ad3b5.hot-update.js",
"/js/app.b4cd025ad67e5076f3dd.hot-update.js": "/js/app.b4cd025ad67e5076f3dd.hot-update.js",
"/js/remote/app.b4cd025ad67e5076f3dd.hot-update.js": "/js/remote/app.b4cd025ad67e5076f3dd.hot-update.js",
"/js/app.bce120743dc001badce8.hot-update.js": "/js/app.bce120743dc001badce8.hot-update.js",
"/js/remote/app.bce120743dc001badce8.hot-update.js": "/js/remote/app.bce120743dc001badce8.hot-update.js",
"/js/app.f70c1c7938c588dc3506.hot-update.js": "/js/app.f70c1c7938c588dc3506.hot-update.js",
"/js/remote/app.f70c1c7938c588dc3506.hot-update.js": "/js/remote/app.f70c1c7938c588dc3506.hot-update.js",
"/js/app.5ea93c3061affd28413e.hot-update.js": "/js/app.5ea93c3061affd28413e.hot-update.js",
"/js/remote/app.5ea93c3061affd28413e.hot-update.js": "/js/remote/app.5ea93c3061affd28413e.hot-update.js",
"/js/app.be5d6718aa5b6fe7e6f3.hot-update.js": "/js/app.be5d6718aa5b6fe7e6f3.hot-update.js",
"/js/remote/app.be5d6718aa5b6fe7e6f3.hot-update.js": "/js/remote/app.be5d6718aa5b6fe7e6f3.hot-update.js",
"/js/app.347705f7a56ab4a8d079.hot-update.js": "/js/app.347705f7a56ab4a8d079.hot-update.js",
"/js/remote/app.347705f7a56ab4a8d079.hot-update.js": "/js/remote/app.347705f7a56ab4a8d079.hot-update.js",
"/js/app.1affb29d7beae60ca4fa.hot-update.js": "/js/app.1affb29d7beae60ca4fa.hot-update.js",
"/js/remote/app.1affb29d7beae60ca4fa.hot-update.js": "/js/remote/app.1affb29d7beae60ca4fa.hot-update.js",
"/js/app.d58882e6462ae5e166ab.hot-update.js": "/js/app.d58882e6462ae5e166ab.hot-update.js",
"/js/remote/app.d58882e6462ae5e166ab.hot-update.js": "/js/remote/app.d58882e6462ae5e166ab.hot-update.js",
"/js/app.1abd5fbecf5fbc706fae.hot-update.js": "/js/app.1abd5fbecf5fbc706fae.hot-update.js",
"/js/remote/app.1abd5fbecf5fbc706fae.hot-update.js": "/js/remote/app.1abd5fbecf5fbc706fae.hot-update.js",
"/js/app.07046724bbe4707390a3.hot-update.js": "/js/app.07046724bbe4707390a3.hot-update.js",
"/js/remote/app.07046724bbe4707390a3.hot-update.js": "/js/remote/app.07046724bbe4707390a3.hot-update.js",
"/js/app.9a65a92b46a6721eccd0.hot-update.js": "/js/app.9a65a92b46a6721eccd0.hot-update.js",
"/js/remote/app.9a65a92b46a6721eccd0.hot-update.js": "/js/remote/app.9a65a92b46a6721eccd0.hot-update.js",
"/js/app.e872f8c5b4873dc2386a.hot-update.js": "/js/app.e872f8c5b4873dc2386a.hot-update.js",
"/js/remote/app.e872f8c5b4873dc2386a.hot-update.js": "/js/remote/app.e872f8c5b4873dc2386a.hot-update.js",
"/js/app.f897f7bfb49264541e33.hot-update.js": "/js/app.f897f7bfb49264541e33.hot-update.js",
"/js/remote/app.f897f7bfb49264541e33.hot-update.js": "/js/remote/app.f897f7bfb49264541e33.hot-update.js",
"/js/app.b5e3e62d60184927979e.hot-update.js": "/js/app.b5e3e62d60184927979e.hot-update.js",
"/js/remote/app.b5e3e62d60184927979e.hot-update.js": "/js/remote/app.b5e3e62d60184927979e.hot-update.js",
"/js/app.a9b143bfe33c3dacdf55.hot-update.js": "/js/app.a9b143bfe33c3dacdf55.hot-update.js",
"/js/remote/app.a9b143bfe33c3dacdf55.hot-update.js": "/js/remote/app.a9b143bfe33c3dacdf55.hot-update.js",
"/js/app.d2a5ba089631c2f77d4b.hot-update.js": "/js/app.d2a5ba089631c2f77d4b.hot-update.js",
"/js/remote/app.d2a5ba089631c2f77d4b.hot-update.js": "/js/remote/app.d2a5ba089631c2f77d4b.hot-update.js",
"/js/app.f2edae57fe62e4a19310.hot-update.js": "/js/app.f2edae57fe62e4a19310.hot-update.js",
"/js/remote/app.f2edae57fe62e4a19310.hot-update.js": "/js/remote/app.f2edae57fe62e4a19310.hot-update.js",
"/js/app.ab20356e38a347cde97b.hot-update.js": "/js/app.ab20356e38a347cde97b.hot-update.js",
"/js/remote/app.ab20356e38a347cde97b.hot-update.js": "/js/remote/app.ab20356e38a347cde97b.hot-update.js",
"/js/app.0453d8403bf655e2f3a4.hot-update.js": "/js/app.0453d8403bf655e2f3a4.hot-update.js",
"/js/remote/app.0453d8403bf655e2f3a4.hot-update.js": "/js/remote/app.0453d8403bf655e2f3a4.hot-update.js",
"/js/app.6e61bf72ae4a5f9fc2f0.hot-update.js": "/js/app.6e61bf72ae4a5f9fc2f0.hot-update.js",
"/js/remote/app.6e61bf72ae4a5f9fc2f0.hot-update.js": "/js/remote/app.6e61bf72ae4a5f9fc2f0.hot-update.js",
"/js/app.df5d50d7bd045d2a0d33.hot-update.js": "/js/app.df5d50d7bd045d2a0d33.hot-update.js",
"/js/remote/app.df5d50d7bd045d2a0d33.hot-update.js": "/js/remote/app.df5d50d7bd045d2a0d33.hot-update.js",
"/js/app.89447c9c8ba1cb8ab43a.hot-update.js": "/js/app.89447c9c8ba1cb8ab43a.hot-update.js",
"/js/remote/app.89447c9c8ba1cb8ab43a.hot-update.js": "/js/remote/app.89447c9c8ba1cb8ab43a.hot-update.js",
"/js/app.e25aeb45d3172fb4ca29.hot-update.js": "/js/app.e25aeb45d3172fb4ca29.hot-update.js",
"/js/remote/app.e25aeb45d3172fb4ca29.hot-update.js": "/js/remote/app.e25aeb45d3172fb4ca29.hot-update.js",
"/js/app.8e95bc0ed78a189004f7.hot-update.js": "/js/app.8e95bc0ed78a189004f7.hot-update.js",
"/js/remote/app.8e95bc0ed78a189004f7.hot-update.js": "/js/remote/app.8e95bc0ed78a189004f7.hot-update.js",
"/js/app.cc95075d83222cf6c7bf.hot-update.js": "/js/app.cc95075d83222cf6c7bf.hot-update.js",
"/js/remote/app.cc95075d83222cf6c7bf.hot-update.js": "/js/remote/app.cc95075d83222cf6c7bf.hot-update.js",
"/js/app.4b9026fc34e8c16d765f.hot-update.js": "/js/app.4b9026fc34e8c16d765f.hot-update.js",
"/js/remote/app.4b9026fc34e8c16d765f.hot-update.js": "/js/remote/app.4b9026fc34e8c16d765f.hot-update.js",
"/js/app.92cd43036fc9c2abec33.hot-update.js": "/js/app.92cd43036fc9c2abec33.hot-update.js",
"/js/remote/app.92cd43036fc9c2abec33.hot-update.js": "/js/remote/app.92cd43036fc9c2abec33.hot-update.js",
"/js/app.ca175b78d8300a690df4.hot-update.js": "/js/app.ca175b78d8300a690df4.hot-update.js",
"/js/remote/app.ca175b78d8300a690df4.hot-update.js": "/js/remote/app.ca175b78d8300a690df4.hot-update.js",
"/js/app.9dd355a3f25777d9ad9d.hot-update.js": "/js/app.9dd355a3f25777d9ad9d.hot-update.js",
"/js/remote/app.9dd355a3f25777d9ad9d.hot-update.js": "/js/remote/app.9dd355a3f25777d9ad9d.hot-update.js",
"/js/app.21bedfd4f2494fc53e77.hot-update.js": "/js/app.21bedfd4f2494fc53e77.hot-update.js",
"/js/remote/app.21bedfd4f2494fc53e77.hot-update.js": "/js/remote/app.21bedfd4f2494fc53e77.hot-update.js",
"/js/app.16ec22ccfdb7a2718d84.hot-update.js": "/js/app.16ec22ccfdb7a2718d84.hot-update.js",
"/js/remote/app.16ec22ccfdb7a2718d84.hot-update.js": "/js/remote/app.16ec22ccfdb7a2718d84.hot-update.js",
"/js/app.556e69b052e81421643a.hot-update.js": "/js/app.556e69b052e81421643a.hot-update.js",
"/js/remote/app.556e69b052e81421643a.hot-update.js": "/js/remote/app.556e69b052e81421643a.hot-update.js",
"/js/app.af59bf00377cbe9d91a7.hot-update.js": "/js/app.af59bf00377cbe9d91a7.hot-update.js",
"/js/remote/app.af59bf00377cbe9d91a7.hot-update.js": "/js/remote/app.af59bf00377cbe9d91a7.hot-update.js",
"/js/app.06aeabf59b842b20602b.hot-update.js": "/js/app.06aeabf59b842b20602b.hot-update.js",
"/js/remote/app.06aeabf59b842b20602b.hot-update.js": "/js/remote/app.06aeabf59b842b20602b.hot-update.js",
"/js/app.e005cf3da9b09857297e.hot-update.js": "/js/app.e005cf3da9b09857297e.hot-update.js",
"/js/remote/app.e005cf3da9b09857297e.hot-update.js": "/js/remote/app.e005cf3da9b09857297e.hot-update.js"
}

View file

@ -75,6 +75,10 @@ Route::group(['namespace' => 'API'], static function (): void {
Route::put('artist/{artist}/image', 'ArtistImageController@update');
Route::get('album/{album}/thumbnail', 'AlbumThumbnailController@get');
Route::group(['namespace' => 'Search', 'prefix' => 'search'], static function (): void {
Route::get('/', 'ExcerptSearchController@index');
});
});
Route::group([