From d45545e7e154da20967655e03251d18eaed4abc1 Mon Sep 17 00:00:00 2001 From: Kyrch Date: Fri, 10 Jan 2025 01:48:54 -0300 Subject: [PATCH] feat: added anime synonyms to series scout search (#772) --- .../DiscordVideoNotificationAction.php | 5 +- .../Models/CanCreateExternalResource.php | 2 +- .../Models/Wiki/Video/ShouldForceThread.php | 28 --------- app/Enums/Models/Wiki/ResourceSite.php | 17 ++++- .../VideoDiscordNotificationBulkAction.php | 9 --- .../Controllers/Api/Pivot/PivotController.php | 2 +- .../Models/{ => Pivot}/AuthorizesPivot.php | 2 +- app/Models/Wiki/Series.php | 28 +++++++++ app/Policies/Wiki/ArtistPolicy.php | 5 -- .../Elasticsearch/Api/Query/ElasticQuery.php | 14 ++--- .../Api/Query/Wiki/SeriesQuery.php | 42 ++++++------- database/seeders/DatabaseSeeder.php | 2 + database/seeders/Scout/ImportModelsSeeder.php | 62 +++++++++++++++++++ .../2020_12_22_034544_create_series_index.php | 55 ++++++++++++++++ lang/en/filament.php | 4 -- 15 files changed, 193 insertions(+), 84 deletions(-) delete mode 100644 app/Enums/Actions/Models/Wiki/Video/ShouldForceThread.php rename app/Http/Middleware/Models/{ => Pivot}/AuthorizesPivot.php (98%) create mode 100644 database/seeders/Scout/ImportModelsSeeder.php diff --git a/app/Actions/Discord/DiscordVideoNotificationAction.php b/app/Actions/Discord/DiscordVideoNotificationAction.php index ce5f7fdda..98c829deb 100644 --- a/app/Actions/Discord/DiscordVideoNotificationAction.php +++ b/app/Actions/Discord/DiscordVideoNotificationAction.php @@ -5,7 +5,6 @@ namespace App\Actions\Discord; use App\Enums\Actions\Models\Wiki\Video\NotificationType; -use App\Enums\Actions\Models\Wiki\Video\ShouldForceThread; use App\Models\Wiki\Video; use Illuminate\Database\Eloquent\Collection; use Illuminate\Support\Arr; @@ -27,7 +26,6 @@ class DiscordVideoNotificationAction public function handle(Collection $videos, array $fields): void { $type = Arr::get($fields, NotificationType::getFieldKey()); - $shouldForce = ShouldForceThread::from(intval(Arr::get($fields, ShouldForceThread::getFieldKey()))); $newVideos = []; @@ -36,7 +34,7 @@ public function handle(Collection $videos, array $fields): void ->load([ 'animethemeentries.animetheme.anime.discordthread', 'animethemeentries.animetheme.anime.images', - 'animethemeentries.animetheme.group', + Video::RELATION_GROUP, 'animethemeentries.animetheme.song.artists', ]); @@ -44,7 +42,6 @@ public function handle(Collection $videos, array $fields): void $anime = $theme->anime; if ($anime->discordthread === null) { - if ($shouldForce === ShouldForceThread::NO) return; $threadAction = new DiscordThreadAction(); diff --git a/app/Concerns/Models/CanCreateExternalResource.php b/app/Concerns/Models/CanCreateExternalResource.php index 1da26122e..d5f8d8e7a 100644 --- a/app/Concerns/Models/CanCreateExternalResource.php +++ b/app/Concerns/Models/CanCreateExternalResource.php @@ -37,7 +37,7 @@ public function createResource(string $url, ResourceSite $site, (BaseModel&HasRe $url = $site->formatResourceLink($model::class, intval($matches[2]), $matches[2], $matches[1]); } - if ($id !== null) { + if ($id !== null && !$site->usesIdInLink()) { $url = $site->formatResourceLink($model::class, intval($id), $id); } } diff --git a/app/Enums/Actions/Models/Wiki/Video/ShouldForceThread.php b/app/Enums/Actions/Models/Wiki/Video/ShouldForceThread.php deleted file mode 100644 index 8e016459b..000000000 --- a/app/Enums/Actions/Models/Wiki/Video/ShouldForceThread.php +++ /dev/null @@ -1,28 +0,0 @@ - false, + default => true, + }; + } + /** * Get the URL capture groups of the resource site. * diff --git a/app/Filament/BulkActions/Models/Wiki/Video/VideoDiscordNotificationBulkAction.php b/app/Filament/BulkActions/Models/Wiki/Video/VideoDiscordNotificationBulkAction.php index 6463ad112..9b6deab11 100644 --- a/app/Filament/BulkActions/Models/Wiki/Video/VideoDiscordNotificationBulkAction.php +++ b/app/Filament/BulkActions/Models/Wiki/Video/VideoDiscordNotificationBulkAction.php @@ -6,7 +6,6 @@ use App\Actions\Discord\DiscordVideoNotificationAction as DiscordVideoNotificationActionAction; use App\Enums\Actions\Models\Wiki\Video\NotificationType; -use App\Enums\Actions\Models\Wiki\Video\ShouldForceThread; use App\Filament\BulkActions\BaseBulkAction; use App\Filament\Components\Fields\Select; use App\Models\Discord\DiscordThread; @@ -70,14 +69,6 @@ public function getForm(Form $form): ?Form ->default(NotificationType::ADDED->value) ->required() ->rules(['required', new Enum(NotificationType::class)]), - - Select::make(ShouldForceThread::getFieldKey()) - ->label(__('filament.bulk_actions.discord.notification.should_force.name')) - ->helperText(__('filament.bulk_actions.discord.notification.should_force.help')) - ->options(ShouldForceThread::asSelectArray()) - ->default(ShouldForceThread::NO->value) - ->required() - ->rules(['required', new Enum(ShouldForceThread::class)]), ]); } } diff --git a/app/Http/Controllers/Api/Pivot/PivotController.php b/app/Http/Controllers/Api/Pivot/PivotController.php index c5e72593a..73bf6a580 100644 --- a/app/Http/Controllers/Api/Pivot/PivotController.php +++ b/app/Http/Controllers/Api/Pivot/PivotController.php @@ -8,7 +8,7 @@ use App\Http\Api\Schema\Schema; use App\Http\Controllers\Controller; use App\Http\Middleware\Auth\Authenticate; -use App\Http\Middleware\Models\AuthorizesPivot; +use App\Http\Middleware\Models\Pivot\AuthorizesPivot; use Illuminate\Support\Str; /** diff --git a/app/Http/Middleware/Models/AuthorizesPivot.php b/app/Http/Middleware/Models/Pivot/AuthorizesPivot.php similarity index 98% rename from app/Http/Middleware/Models/AuthorizesPivot.php rename to app/Http/Middleware/Models/Pivot/AuthorizesPivot.php index 6e5de7107..0c768f80d 100644 --- a/app/Http/Middleware/Models/AuthorizesPivot.php +++ b/app/Http/Middleware/Models/Pivot/AuthorizesPivot.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace App\Http\Middleware\Models; +namespace App\Http\Middleware\Models\Pivot; use App\Models\Auth\User; use Closure; diff --git a/app/Models/Wiki/Series.php b/app/Models/Wiki/Series.php index fc9e7e05c..64c316eab 100644 --- a/app/Models/Wiki/Series.php +++ b/app/Models/Wiki/Series.php @@ -14,6 +14,7 @@ use App\Pivots\Wiki\AnimeSeries; use Database\Factories\Wiki\SeriesFactory; use Elastic\ScoutDriverPlus\Searchable; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Support\Collection; @@ -39,6 +40,7 @@ class Series extends BaseModel final public const ATTRIBUTE_SLUG = 'slug'; final public const RELATION_ANIME = 'anime'; + final public const RELATION_ANIME_SYNONYMS = 'anime.animesynonyms'; /** * The attributes that are mass assignable. @@ -78,6 +80,32 @@ class Series extends BaseModel */ protected $primaryKey = Series::ATTRIBUTE_ID; + /** + * Modify the query used to retrieve models when making all of the models searchable. + * + * @param Builder $query + * @return Builder + */ + protected function makeAllSearchableUsing(Builder $query): Builder + { + return $query->with(Series::RELATION_ANIME_SYNONYMS); + } + + /** + * Get the indexable data array for the model. + * + * @return array + */ + public function toSearchableArray(): array + { + $array = $this->toArray(); + $array['anime'] = $this->anime->map( + fn (Anime $anime) => $anime->toSearchableArray() + )->toArray(); + + return $array; + } + /** * Get the route key for the model. * diff --git a/app/Policies/Wiki/ArtistPolicy.php b/app/Policies/Wiki/ArtistPolicy.php index 9d5caf6d2..145f1b1dc 100644 --- a/app/Policies/Wiki/ArtistPolicy.php +++ b/app/Policies/Wiki/ArtistPolicy.php @@ -126,11 +126,6 @@ public function attachAnyArtist(User $user): bool */ public function attachArtist(User $user, Artist $artist, Artist $artist2): bool { - if ($artist->is($artist2)) { - // An artist cannot be a member/group of themselves - return false; - } - $attached = ArtistMember::query() ->where(ArtistMember::ATTRIBUTE_ARTIST, $artist->getKey()) ->where(ArtistMember::ATTRIBUTE_MEMBER, $artist2->getKey()) diff --git a/app/Scout/Elasticsearch/Api/Query/ElasticQuery.php b/app/Scout/Elasticsearch/Api/Query/ElasticQuery.php index e10173dcd..cb1780fd4 100644 --- a/app/Scout/Elasticsearch/Api/Query/ElasticQuery.php +++ b/app/Scout/Elasticsearch/Api/Query/ElasticQuery.php @@ -27,8 +27,8 @@ abstract public function build(Criteria $criteria): SearchParametersBuilder; * - Matching at least one term (word) (x0.6) * - Matching fuzzy (x0.4) * - * @param string $field - * @param string $searchTerm + * @param string $field + * @param string $searchTerm * @return array */ protected function createTextQuery(string $field, string $searchTerm): array @@ -71,8 +71,8 @@ protected function createTextQuery(string $field, string $searchTerm): array /** * Helper function for raw queries. This will wrap queries in nested queries. * - * @param string $nestedResource - * @param array $nestedQueries + * @param string $nestedResource + * @param array $nestedQueries * @return array */ protected function createNestedQuery(string $nestedResource, array $nestedQueries): array @@ -92,9 +92,9 @@ protected function createNestedQuery(string $nestedResource, array $nestedQuerie * Shorthand function for calling `$this->createNestedQuery()` with the output from * `$this->createTextQuery()`. * - * @param string $nestedResource - * @param string $field - * @param string $searchTerm + * @param string $nestedResource + * @param string $field + * @param string $searchTerm * @return array */ protected function createNestedTextQuery(string $nestedResource, string $field, string $searchTerm): array diff --git a/app/Scout/Elasticsearch/Api/Query/Wiki/SeriesQuery.php b/app/Scout/Elasticsearch/Api/Query/Wiki/SeriesQuery.php index 374b8f47c..03c4b53ad 100644 --- a/app/Scout/Elasticsearch/Api/Query/Wiki/SeriesQuery.php +++ b/app/Scout/Elasticsearch/Api/Query/Wiki/SeriesQuery.php @@ -7,8 +7,6 @@ use App\Http\Api\Criteria\Search\Criteria; use App\Models\Wiki\Series; use App\Scout\Elasticsearch\Api\Query\ElasticQuery; -use Elastic\ScoutDriverPlus\Builders\MatchPhraseQueryBuilder; -use Elastic\ScoutDriverPlus\Builders\MatchQueryBuilder; use Elastic\ScoutDriverPlus\Builders\SearchParametersBuilder; use Elastic\ScoutDriverPlus\Support\Query; @@ -26,26 +24,26 @@ class SeriesQuery extends ElasticQuery public function build(Criteria $criteria): SearchParametersBuilder { $query = Query::bool() - ->should( - new MatchPhraseQueryBuilder() - ->field('name') - ->query($criteria->getTerm()) - ) - ->should( - new MatchQueryBuilder() - ->field('name') - ->query($criteria->getTerm()) - ->operator('AND') - ) - ->should( - new MatchQueryBuilder() - ->field('name') - ->query($criteria->getTerm()) - ->operator('AND') - ->lenient(true) - ->fuzziness('AUTO') - ) - ->minimumShouldMatch(1); + ->mustRaw([ + 'dis_max' => [ + 'queries' => [ + [ + 'bool' => [ + 'should' => $this->createTextQuery('name', $criteria->getTerm()) + ] + ], + [ + 'bool' => [ + 'boost' => 0.6, + 'should' => $this->createNestedQuery( + 'anime', + $this->createNestedTextQuery('anime.synonyms', 'text', $criteria->getTerm()) + ) + ] + ] + ] + ] + ]); return Series::searchQuery($query); } diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 9f6207bd1..e2b8164a6 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -7,6 +7,7 @@ use Database\Seeders\Admin\Feature\FeatureSeeder; use Database\Seeders\Auth\Permission\PermissionSeeder; use Database\Seeders\Auth\Role\RoleSeeder; +use Database\Seeders\Scout\ImportModelsSeeder; use Database\Seeders\Wiki\Audio\AudioSeeder; use Database\Seeders\Wiki\Video\VideoSeeder; use Illuminate\Database\Seeder; @@ -29,5 +30,6 @@ public function run(): void $this->call(AudioSeeder::class); $this->call(HashidsSeeder::class); $this->call(FeatureSeeder::class); + $this->call(ImportModelsSeeder::class); } } diff --git a/database/seeders/Scout/ImportModelsSeeder.php b/database/seeders/Scout/ImportModelsSeeder.php new file mode 100644 index 000000000..902ed9215 --- /dev/null +++ b/database/seeders/Scout/ImportModelsSeeder.php @@ -0,0 +1,62 @@ +command->info('No driver configured for Scout. Skipping models importing.'); + return; + } + + $this->scoutImport(Playlist::class); + $this->scoutImport(Anime::class); + $this->scoutImport(AnimeSynonym::class); + $this->scoutImport(AnimeTheme::class); + $this->scoutImport(AnimeThemeEntry::class); + $this->scoutImport(Artist::class); + $this->scoutImport(Series::class); + $this->scoutImport(Song::class); + $this->scoutImport(Studio::class); + $this->scoutImport(Video::class); + } + + /** + * Call the scout import command for the given model. + * + * @param string $modelClass + * @return void + */ + private function scoutImport(string $modelClass): void + { + $this->command->info("Importing Models for {$modelClass}"); + Artisan::call('scout:import', ['model' => $modelClass]); + } +} diff --git a/elastic/migrations/2020_12_22_034544_create_series_index.php b/elastic/migrations/2020_12_22_034544_create_series_index.php index 0d1dac63b..1199b1ab1 100644 --- a/elastic/migrations/2020_12_22_034544_create_series_index.php +++ b/elastic/migrations/2020_12_22_034544_create_series_index.php @@ -34,6 +34,61 @@ public function up(): void ], ]); $mapping->date('updated_at'); + $mapping->nested('anime', [ + 'properties' => [ + 'anime_id' => [ + 'type' => 'long', + ], + 'created_at' => [ + 'type' => 'date', + ], + 'name' => [ + 'type' => 'text', + 'copy_to' => ['anime_slug'], + ], + 'season' => [ + 'type' => 'long', + ], + 'slug' => [ + 'type' => 'text', + ], + 'synonyms' => [ + 'type' => 'nested', + 'properties' => [ + 'anime_id' => [ + 'type' => 'long', + ], + 'created_at' => [ + 'type' => 'date', + ], + 'synonym_id' => [ + 'type' => 'long', + ], + 'text' => [ + 'type' => 'text', + 'copy_to' => ['synonym_slug'], + ], + 'type' => [ + 'type' => 'long', + ], + 'updated_at' => [ + 'type' => 'date', + ], + ], + ], + 'synopsis' => [ + 'type' => 'text', + ], + 'updated_at' => [ + 'type' => 'date', + ], + 'year' => [ + 'type' => 'long', + ], + ] + ]); + $mapping->text('anime_slug'); + $mapping->text('synonym_slug'); }); } diff --git a/lang/en/filament.php b/lang/en/filament.php index e8a5c80d1..c9efea836 100644 --- a/lang/en/filament.php +++ b/lang/en/filament.php @@ -441,10 +441,6 @@ 'notification' => [ 'icon' => 'heroicon-o-bell', 'name' => 'Create Discord Notification', - 'should_force' => [ - 'help' => 'If yes, the thread will be created if it does not exist', - 'name' => 'Should force thread?', - ], 'should_send' => [ 'help' => 'If yes, the notification will be created.', 'name' => 'Should send notification?',