From 7d676d301027f1315cd00aa4b20c3372ddc2496d Mon Sep 17 00:00:00 2001 From: Kyrch Date: Sun, 8 Feb 2026 01:46:02 -0300 Subject: [PATCH] feat: improve search with word_delimiter_graph (#1086) --- app/Actions/Fortify/CreateNewUser.php | 2 +- .../Fortify/UpdateUserProfileInformation.php | 2 +- .../Elastic/ConfiguresTextAnalyzers.php | 26 ++++++++ app/Filament/Resources/Wiki/VideoResource.php | 13 +++- .../Fields/Wiki/Video/VideoTagsField.php | 2 +- .../Playlist/PlaylistDescriptionField.php | 1 - .../Api/Field/Wiki/Video/VideoTagsField.php | 4 +- app/Models/Wiki/Video.php | 64 ++++++++++--------- database/seeders/Scout/ImportModelsSeeder.php | 9 +++ .../2020_12_21_225415_create_anime_index.php | 10 ++- .../2020_12_22_033019_create_artist_index.php | 41 +++++++++--- .../2020_12_22_034505_create_entry_index.php | 15 ++++- .../2020_12_22_034544_create_series_index.php | 11 +++- .../2020_12_22_034549_create_song_index.php | 10 ++- ...2020_12_22_034553_create_synonym_index.php | 10 ++- .../2020_12_22_034557_create_theme_index.php | 16 ++++- .../2020_12_22_034601_create_video_index.php | 17 ++++- .../2021_08_20_213637_create_studio_index.php | 9 ++- ...022_10_19_050811_create_playlist_index.php | 9 ++- ...2025_05_14_001128_create_profile_index.php | 9 ++- lang/en/filament.php | 3 + .../External/ExternalProfileStoreTest.php | 32 ---------- 22 files changed, 223 insertions(+), 92 deletions(-) create mode 100644 app/Concerns/Elastic/ConfiguresTextAnalyzers.php diff --git a/app/Actions/Fortify/CreateNewUser.php b/app/Actions/Fortify/CreateNewUser.php index 3b59a4724..530af5ddb 100644 --- a/app/Actions/Fortify/CreateNewUser.php +++ b/app/Actions/Fortify/CreateNewUser.php @@ -26,7 +26,7 @@ class CreateNewUser implements CreatesNewUsers public function create(array $input): User { Validator::make($input, [ - User::ATTRIBUTE_NAME => ['required', 'string', 'max:255', 'alpha_dash', Rule::unique(User::class), new ModerationRule()], + User::ATTRIBUTE_NAME => ['required', 'string', 'max:35', 'alpha_dash', Rule::unique(User::class), new ModerationRule()], User::ATTRIBUTE_EMAIL => ['required', 'string', 'email', 'max:255', 'indisposable', Rule::unique(User::class)], User::ATTRIBUTE_PASSWORD => Password::required(), 'terms' => ['required'], diff --git a/app/Actions/Fortify/UpdateUserProfileInformation.php b/app/Actions/Fortify/UpdateUserProfileInformation.php index 69fc98841..42a31640c 100644 --- a/app/Actions/Fortify/UpdateUserProfileInformation.php +++ b/app/Actions/Fortify/UpdateUserProfileInformation.php @@ -24,7 +24,7 @@ class UpdateUserProfileInformation implements UpdatesUserProfileInformation public function update(User $user, array $input): void { $validated = Validator::make($input, [ - User::ATTRIBUTE_NAME => ['required_without:'.User::ATTRIBUTE_EMAIL, 'string', 'max:255', 'alpha_dash', Rule::unique(User::class)->ignore($user->id), new ModerationRule()], + User::ATTRIBUTE_NAME => ['required_without:'.User::ATTRIBUTE_EMAIL, 'string', 'max:35', 'alpha_dash', Rule::unique(User::class)->ignore($user->id), new ModerationRule()], User::ATTRIBUTE_EMAIL => ['required_without:'.User::ATTRIBUTE_NAME, 'string', 'email', 'max:255', 'indisposable', Rule::unique(User::class)->ignore($user->id)], ])->validate(); diff --git a/app/Concerns/Elastic/ConfiguresTextAnalyzers.php b/app/Concerns/Elastic/ConfiguresTextAnalyzers.php new file mode 100644 index 000000000..7d316f034 --- /dev/null +++ b/app/Concerns/Elastic/ConfiguresTextAnalyzers.php @@ -0,0 +1,26 @@ +analysis([ + 'analyzer' => [ + 'name_search' => [ + 'type' => 'custom', + 'tokenizer' => 'standard', + 'filter' => [ + 'lowercase', + 'word_delimiter_graph', + ], + ], + ], + ]); + } +} diff --git a/app/Filament/Resources/Wiki/VideoResource.php b/app/Filament/Resources/Wiki/VideoResource.php index 8e8f5ee8e..dfce9804a 100644 --- a/app/Filament/Resources/Wiki/VideoResource.php +++ b/app/Filament/Resources/Wiki/VideoResource.php @@ -136,6 +136,10 @@ class VideoResource extends BaseResource TextColumn::make(Video::ATTRIBUTE_ID) ->label(__('filament.fields.base.id')), + TextColumn::make(Video::ATTRIBUTE_FILENAME) + ->label(__('filament.fields.video.filename.name')) + ->copyableWithMessage(), + TextColumn::make(Video::ATTRIBUTE_RESOLUTION) ->label(__('filament.fields.video.resolution.name')), @@ -163,9 +167,9 @@ class VideoResource extends BaseResource ->label(__('filament.fields.video.source.name')) ->formatStateUsing(fn (VideoSource $state): ?string => $state->localize()), - TextColumn::make(Video::ATTRIBUTE_FILENAME) - ->label(__('filament.fields.video.filename.name')) - ->copyableWithMessage(), + TextColumn::make(Video::ATTRIBUTE_TAGS) + ->label(__('filament.fields.video.tags.name')) + ->sortable(false), ]) ->searchable(); } @@ -179,6 +183,9 @@ class VideoResource extends BaseResource TextEntry::make(Video::ATTRIBUTE_ID) ->label(__('filament.fields.base.id')), + TextEntry::make(Video::ATTRIBUTE_TAGS) + ->label(__('filament.fields.video.tags.name')), + TextEntry::make(Video::ATTRIBUTE_OVERLAP) ->label(__('filament.fields.video.overlap.name')) ->formatStateUsing(fn (VideoOverlap $state): ?string => $state->localize()), diff --git a/app/GraphQL/Schema/Fields/Wiki/Video/VideoTagsField.php b/app/GraphQL/Schema/Fields/Wiki/Video/VideoTagsField.php index 1acfce725..ef0569771 100644 --- a/app/GraphQL/Schema/Fields/Wiki/Video/VideoTagsField.php +++ b/app/GraphQL/Schema/Fields/Wiki/Video/VideoTagsField.php @@ -28,6 +28,6 @@ class VideoTagsField extends StringField */ public function resolve($root, array $args, $context, ResolveInfo $resolveInfo): mixed { - return implode('', $root->getAttribute($this->getColumn())); + return $root->getAttribute($this->getColumn()); } } diff --git a/app/Http/Api/Field/List/Playlist/PlaylistDescriptionField.php b/app/Http/Api/Field/List/Playlist/PlaylistDescriptionField.php index 67d1628e8..bf7ea6367 100644 --- a/app/Http/Api/Field/List/Playlist/PlaylistDescriptionField.php +++ b/app/Http/Api/Field/List/Playlist/PlaylistDescriptionField.php @@ -9,7 +9,6 @@ use App\Contracts\Http\Api\Field\UpdatableField; use App\Http\Api\Field\StringField; use App\Http\Api\Schema\Schema; use App\Models\List\Playlist; -use App\Rules\ModerationRule; use Illuminate\Http\Request; class PlaylistDescriptionField extends StringField implements CreatableField, UpdatableField diff --git a/app/Http/Api/Field/Wiki/Video/VideoTagsField.php b/app/Http/Api/Field/Wiki/Video/VideoTagsField.php index f033d25b3..f68da8856 100644 --- a/app/Http/Api/Field/Wiki/Video/VideoTagsField.php +++ b/app/Http/Api/Field/Wiki/Video/VideoTagsField.php @@ -28,8 +28,6 @@ class VideoTagsField extends Field implements RenderableField public function render(Model $model): string { - $tags = $model->getAttribute($this->getColumn()); - - return implode('', $tags); + return $model->getAttribute($this->getColumn()); } } diff --git a/app/Models/Wiki/Video.php b/app/Models/Wiki/Video.php index ba1bcea21..b84504165 100644 --- a/app/Models/Wiki/Video.php +++ b/app/Models/Wiki/Video.php @@ -198,24 +198,26 @@ class Video extends BaseModel implements Auditable, HasAggregateViews, SoftDelet */ protected function priority(): Attribute { - $priority = intval($this->source?->getPriority()); + return Attribute::make(function (): int { + $priority = intval($this->source?->getPriority()); - // Videos that play over the episode will likely have compressed audio - if ($this->overlap === VideoOverlap::OVER) { - $priority -= 8; - } + // Videos that play over the episode will likely have compressed audio + if ($this->overlap === VideoOverlap::OVER) { + $priority -= 8; + } - // Videos that transition to or from the episode may have compressed audio - if ($this->overlap === VideoOverlap::TRANS) { - $priority -= 5; - } + // Videos that transition to or from the episode may have compressed audio + if ($this->overlap === VideoOverlap::TRANS) { + $priority -= 5; + } - // De-prioritize hardsubbed videos - if ($this->lyrics || $this->subbed) { - $priority--; - } + // De-prioritize hardsubbed videos + if ($this->lyrics || $this->subbed) { + $priority--; + } - return Attribute::make(fn (): int => $priority); + return $priority; + }); } /** @@ -223,25 +225,27 @@ class Video extends BaseModel implements Auditable, HasAggregateViews, SoftDelet */ protected function tags(): Attribute { - $tags = []; + return Attribute::make(function (): string { + $tags = []; - if ($this->nc) { - $tags[] = 'NC'; - } - if ($this->source === VideoSource::BD || $this->source === VideoSource::DVD) { - $tags[] = $this->source->localize(); - } - if (filled($this->resolution) && $this->resolution !== 720) { - $tags[] = strval($this->resolution); - } + if ($this->nc) { + $tags[] = 'NC'; + } + if ($this->source === VideoSource::BD || $this->source === VideoSource::DVD) { + $tags[] = $this->source->localize(); + } + if (filled($this->resolution) && $this->resolution !== 720) { + $tags[] = strval($this->resolution); + } - if ($this->subbed) { - $tags[] = 'Subbed'; - } elseif ($this->lyrics) { - $tags[] = 'Lyrics'; - } + if ($this->subbed) { + $tags[] = 'Subbed'; + } elseif ($this->lyrics) { + $tags[] = 'Lyrics'; + } - return Attribute::make(fn (): array => $tags); + return implode('', $tags); + }); } /** diff --git a/database/seeders/Scout/ImportModelsSeeder.php b/database/seeders/Scout/ImportModelsSeeder.php index 209157940..08a6e0e69 100644 --- a/database/seeders/Scout/ImportModelsSeeder.php +++ b/database/seeders/Scout/ImportModelsSeeder.php @@ -26,6 +26,12 @@ class ImportModelsSeeder extends Seeder */ public function run(): void { + if (Config::get('app.env') === 'staging') { + $this->command->alert('This seeder is not allowed in staging environments.'); + + return; + } + $driver = Config::get('scout.driver'); if (blank($driver)) { $this->command->info('No driver configured for Scout. Skipping models importing.'); @@ -33,6 +39,9 @@ class ImportModelsSeeder extends Seeder return; } + $this->command->info('Running elastic migrations'); + Artisan::call('elastic:migrate'); + $this->scoutImport(Playlist::class); $this->scoutImport(Anime::class); $this->scoutImport(AnimeSynonym::class); diff --git a/elastic/migrations/2020_12_21_225415_create_anime_index.php b/elastic/migrations/2020_12_21_225415_create_anime_index.php index f444839cb..c159f4ef7 100644 --- a/elastic/migrations/2020_12_21_225415_create_anime_index.php +++ b/elastic/migrations/2020_12_21_225415_create_anime_index.php @@ -2,21 +2,28 @@ declare(strict_types=1); +use App\Concerns\Elastic\ConfiguresTextAnalyzers; use Elastic\Adapter\Indices\Mapping; +use Elastic\Adapter\Indices\Settings; use Elastic\Migrations\Facades\Index; use Elastic\Migrations\MigrationInterface; final class CreateAnimeIndex implements MigrationInterface { + use ConfiguresTextAnalyzers; + /** * Run the migration. */ public function up(): void { - Index::createIfNotExists('anime', function (Mapping $mapping) { + Index::create('anime', function (Mapping $mapping, Settings $settings) { + $this->configureTextAnalyzers($settings); + $mapping->long('anime_id'); $mapping->date('created_at'); $mapping->text('name', [ + 'analyzer' => 'name_search', 'fields' => [ 'keyword' => [ 'type' => 'keyword', @@ -45,6 +52,7 @@ final class CreateAnimeIndex implements MigrationInterface ], 'text' => [ 'type' => 'text', + 'analyzer' => 'name_search', ], 'type' => [ 'type' => 'long', diff --git a/elastic/migrations/2020_12_22_033019_create_artist_index.php b/elastic/migrations/2020_12_22_033019_create_artist_index.php index e65887852..9e3fc404d 100644 --- a/elastic/migrations/2020_12_22_033019_create_artist_index.php +++ b/elastic/migrations/2020_12_22_033019_create_artist_index.php @@ -2,21 +2,28 @@ declare(strict_types=1); +use App\Concerns\Elastic\ConfiguresTextAnalyzers; use Elastic\Adapter\Indices\Mapping; +use Elastic\Adapter\Indices\Settings; use Elastic\Migrations\Facades\Index; use Elastic\Migrations\MigrationInterface; final class CreateArtistIndex implements MigrationInterface { + use ConfiguresTextAnalyzers; + /** * Run the migration. */ public function up(): void { - Index::createIfNotExists('artists', function (Mapping $mapping) { + Index::createIfNotExists('artists', function (Mapping $mapping, Settings $settings) { + $this->configureTextAnalyzers($settings); + $mapping->long('artist_id'); $mapping->date('created_at'); $mapping->text('name', [ + 'analyzer' => 'name_search', 'fields' => [ 'keyword' => [ 'type' => 'keyword', @@ -39,13 +46,31 @@ final class CreateArtistIndex implements MigrationInterface ]); $mapping->nested('performances', [ 'properties' => [ - 'performance_id' => ['type' => 'long'], - 'created_at' => ['type' => 'date'], - 'updated_at' => ['type' => 'date'], - 'alias' => ['type' => 'text'], - 'as' => ['type' => 'text'], - 'membership_alias' => ['type' => 'text'], - 'membership_as' => ['type' => 'text'], + 'performance_id' => [ + 'type' => 'long', + ], + 'created_at' => [ + 'type' => 'date', + ], + 'updated_at' => [ + 'type' => 'date', + ], + 'alias' => [ + 'type' => 'text', + 'analyzer' => 'name_search', + ], + 'as' => [ + 'type' => 'text', + 'analyzer' => 'name_search', + ], + 'membership_alias' => [ + 'type' => 'text', + 'analyzer' => 'name_search', + ], + 'membership_as' => [ + 'type' => 'text', + 'analyzer' => 'name_search', + ], ], ]); $mapping->date('updated_at'); diff --git a/elastic/migrations/2020_12_22_034505_create_entry_index.php b/elastic/migrations/2020_12_22_034505_create_entry_index.php index 31f19f07d..e578826b4 100644 --- a/elastic/migrations/2020_12_22_034505_create_entry_index.php +++ b/elastic/migrations/2020_12_22_034505_create_entry_index.php @@ -2,18 +2,24 @@ declare(strict_types=1); +use App\Concerns\Elastic\ConfiguresTextAnalyzers; use Elastic\Adapter\Indices\Mapping; +use Elastic\Adapter\Indices\Settings; use Elastic\Migrations\Facades\Index; use Elastic\Migrations\MigrationInterface; final class CreateEntryIndex implements MigrationInterface { + use ConfiguresTextAnalyzers; + /** * Run the migration. */ public function up(): void { - Index::createIfNotExists('anime_theme_entries', function (Mapping $mapping) { + Index::createIfNotExists('anime_theme_entries', function (Mapping $mapping, Settings $settings) { + $this->configureTextAnalyzers($settings); + $mapping->text('anime_slug'); $mapping->date('created_at'); $mapping->long('entry_id'); @@ -48,6 +54,7 @@ final class CreateEntryIndex implements MigrationInterface 'name' => [ 'type' => 'text', 'copy_to' => ['anime_slug'], + 'analyzer' => 'name_search', ], 'season' => [ 'type' => 'long', @@ -70,6 +77,7 @@ final class CreateEntryIndex implements MigrationInterface 'text' => [ 'type' => 'text', 'copy_to' => ['synonym_slug'], + 'analyzer' => 'name_search', ], 'type' => [ 'type' => 'long', @@ -141,6 +149,11 @@ final class CreateEntryIndex implements MigrationInterface ], 'title' => [ 'type' => 'text', + 'analyzer' => 'name_search', + ], + 'title_native' => [ + 'type' => 'text', + 'analyzer' => 'name_search', ], 'updated_at' => [ 'type' => 'date', 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 6c9cda78c..2fafeec2d 100644 --- a/elastic/migrations/2020_12_22_034544_create_series_index.php +++ b/elastic/migrations/2020_12_22_034544_create_series_index.php @@ -2,20 +2,27 @@ declare(strict_types=1); +use App\Concerns\Elastic\ConfiguresTextAnalyzers; use Elastic\Adapter\Indices\Mapping; +use Elastic\Adapter\Indices\Settings; use Elastic\Migrations\Facades\Index; use Elastic\Migrations\MigrationInterface; final class CreateSeriesIndex implements MigrationInterface { + use ConfiguresTextAnalyzers; + /** * Run the migration. */ public function up(): void { - Index::createIfNotExists('series', function (Mapping $mapping) { + Index::createIfNotExists('series', function (Mapping $mapping, Settings $settings) { + $this->configureTextAnalyzers($settings); + $mapping->date('created_at'); $mapping->text('name', [ + 'analyzer' => 'name_search', 'fields' => [ 'keyword' => [ 'type' => 'keyword', @@ -42,6 +49,7 @@ final class CreateSeriesIndex implements MigrationInterface 'name' => [ 'type' => 'text', 'copy_to' => ['anime_slug'], + 'analyzer' => 'name_search', ], 'season' => [ 'type' => 'long', @@ -64,6 +72,7 @@ final class CreateSeriesIndex implements MigrationInterface 'text' => [ 'type' => 'text', 'copy_to' => ['synonym_slug'], + 'analyzer' => 'name_search', ], 'type' => [ 'type' => 'long', diff --git a/elastic/migrations/2020_12_22_034549_create_song_index.php b/elastic/migrations/2020_12_22_034549_create_song_index.php index 6f1d2e9fd..0e1f301a4 100644 --- a/elastic/migrations/2020_12_22_034549_create_song_index.php +++ b/elastic/migrations/2020_12_22_034549_create_song_index.php @@ -2,21 +2,28 @@ declare(strict_types=1); +use App\Concerns\Elastic\ConfiguresTextAnalyzers; use Elastic\Adapter\Indices\Mapping; +use Elastic\Adapter\Indices\Settings; use Elastic\Migrations\Facades\Index; use Elastic\Migrations\MigrationInterface; final class CreateSongIndex implements MigrationInterface { + use ConfiguresTextAnalyzers; + /** * Run the migration. */ public function up(): void { - Index::createIfNotExists('songs', function (Mapping $mapping) { + Index::createIfNotExists('songs', function (Mapping $mapping, Settings $settings) { + $this->configureTextAnalyzers($settings); + $mapping->date('created_at'); $mapping->long('song_id'); $mapping->text('title', [ + 'analyzer' => 'name_search', 'fields' => [ 'keyword' => [ 'type' => 'keyword', @@ -24,6 +31,7 @@ final class CreateSongIndex implements MigrationInterface ], ]); $mapping->text('title_native', [ + 'analyzer' => 'name_search', 'fields' => [ 'keyword' => [ 'type' => 'keyword', diff --git a/elastic/migrations/2020_12_22_034553_create_synonym_index.php b/elastic/migrations/2020_12_22_034553_create_synonym_index.php index 99dba49a7..0364763b6 100644 --- a/elastic/migrations/2020_12_22_034553_create_synonym_index.php +++ b/elastic/migrations/2020_12_22_034553_create_synonym_index.php @@ -2,18 +2,24 @@ declare(strict_types=1); +use App\Concerns\Elastic\ConfiguresTextAnalyzers; use Elastic\Adapter\Indices\Mapping; +use Elastic\Adapter\Indices\Settings; use Elastic\Migrations\Facades\Index; use Elastic\Migrations\MigrationInterface; final class CreateSynonymIndex implements MigrationInterface { + use ConfiguresTextAnalyzers; + /** * Run the migration. */ public function up(): void { - Index::createIfNotExists('anime_synonyms', function (Mapping $mapping) { + Index::createIfNotExists('anime_synonyms', function (Mapping $mapping, Settings $settings) { + $this->configureTextAnalyzers($settings); + $mapping->nested('anime', [ 'properties' => [ 'anime_id' => [ @@ -24,6 +30,7 @@ final class CreateSynonymIndex implements MigrationInterface ], 'name' => [ 'type' => 'text', + 'analyzer' => 'name_search', ], 'media_format' => [ 'type' => 'long', @@ -49,6 +56,7 @@ final class CreateSynonymIndex implements MigrationInterface $mapping->date('created_at'); $mapping->long('synonym_id'); $mapping->text('text', [ + 'analyzer' => 'name_search', 'fields' => [ 'keyword' => [ 'type' => 'keyword', diff --git a/elastic/migrations/2020_12_22_034557_create_theme_index.php b/elastic/migrations/2020_12_22_034557_create_theme_index.php index 034b44425..0f53caeb7 100644 --- a/elastic/migrations/2020_12_22_034557_create_theme_index.php +++ b/elastic/migrations/2020_12_22_034557_create_theme_index.php @@ -2,18 +2,24 @@ declare(strict_types=1); +use App\Concerns\Elastic\ConfiguresTextAnalyzers; use Elastic\Adapter\Indices\Mapping; +use Elastic\Adapter\Indices\Settings; use Elastic\Migrations\Facades\Index; use Elastic\Migrations\MigrationInterface; final class CreateThemeIndex implements MigrationInterface { + use ConfiguresTextAnalyzers; + /** * Run the migration. */ public function up(): void { - Index::createIfNotExists('anime_themes', function (Mapping $mapping) { + Index::createIfNotExists('anime_themes', function (Mapping $mapping, Settings $settings) { + $this->configureTextAnalyzers($settings); + $mapping->nested('anime', [ 'properties' => [ 'anime_id' => [ @@ -25,6 +31,7 @@ final class CreateThemeIndex implements MigrationInterface 'name' => [ 'type' => 'text', 'copy_to' => ['anime_slug'], + 'analyzer' => 'name_search', ], 'season' => [ 'type' => 'long', @@ -47,6 +54,7 @@ final class CreateThemeIndex implements MigrationInterface 'text' => [ 'type' => 'text', 'copy_to' => ['synonym_slug'], + 'analyzer' => 'name_search', ], 'type' => [ 'type' => 'long', @@ -112,9 +120,11 @@ final class CreateThemeIndex implements MigrationInterface ], 'title' => [ 'type' => 'text', + 'analyzer' => 'name_search', ], - 'title_keyword' => [ - 'type' => 'keyword', + 'title_native' => [ + 'type' => 'text', + 'analyzer' => 'name_search', ], 'updated_at' => [ 'type' => 'date', diff --git a/elastic/migrations/2020_12_22_034601_create_video_index.php b/elastic/migrations/2020_12_22_034601_create_video_index.php index 643973522..6ed72092d 100644 --- a/elastic/migrations/2020_12_22_034601_create_video_index.php +++ b/elastic/migrations/2020_12_22_034601_create_video_index.php @@ -2,20 +2,27 @@ declare(strict_types=1); +use App\Concerns\Elastic\ConfiguresTextAnalyzers; use Elastic\Adapter\Indices\Mapping; +use Elastic\Adapter\Indices\Settings; use Elastic\Migrations\Facades\Index; use Elastic\Migrations\MigrationInterface; final class CreateVideoIndex implements MigrationInterface { + use ConfiguresTextAnalyzers; + /** * Run the migration. */ public function up(): void { - Index::createIfNotExists('videos', function (Mapping $mapping) { + Index::createIfNotExists('videos', function (Mapping $mapping, Settings $settings) { + $this->configureTextAnalyzers($settings); + $mapping->text('anime_slug'); $mapping->text('basename', [ + 'analyzer' => 'name_search', 'fields' => [ 'keyword' => [ 'type' => 'keyword', @@ -58,6 +65,7 @@ final class CreateVideoIndex implements MigrationInterface 'name' => [ 'type' => 'text', 'copy_to' => ['anime_slug'], + 'analyzer' => 'name_search', ], 'season' => [ 'type' => 'long', @@ -80,6 +88,7 @@ final class CreateVideoIndex implements MigrationInterface 'text' => [ 'type' => 'text', 'copy_to' => ['synonym_slug'], + 'analyzer' => 'name_search', ], 'type' => [ 'type' => 'long', @@ -152,6 +161,11 @@ final class CreateVideoIndex implements MigrationInterface ], 'title' => [ 'type' => 'text', + 'analyzer' => 'name_search', + ], + 'title_native' => [ + 'type' => 'text', + 'analyzer' => 'name_search', ], 'updated_at' => [ 'type' => 'date', @@ -190,6 +204,7 @@ final class CreateVideoIndex implements MigrationInterface ], ]); $mapping->text('filename', [ + 'analyzer' => 'name_search', 'fields' => [ 'keyword' => [ 'type' => 'keyword', diff --git a/elastic/migrations/2021_08_20_213637_create_studio_index.php b/elastic/migrations/2021_08_20_213637_create_studio_index.php index 2909b869c..1ebdab889 100644 --- a/elastic/migrations/2021_08_20_213637_create_studio_index.php +++ b/elastic/migrations/2021_08_20_213637_create_studio_index.php @@ -2,20 +2,27 @@ declare(strict_types=1); +use App\Concerns\Elastic\ConfiguresTextAnalyzers; use Elastic\Adapter\Indices\Mapping; +use Elastic\Adapter\Indices\Settings; use Elastic\Migrations\Facades\Index; use Elastic\Migrations\MigrationInterface; final class CreateStudioIndex implements MigrationInterface { + use ConfiguresTextAnalyzers; + /** * Run the migration. */ public function up(): void { - Index::createIfNotExists('studios', function (Mapping $mapping) { + Index::createIfNotExists('studios', function (Mapping $mapping, Settings $settings) { + $this->configureTextAnalyzers($settings); + $mapping->date('created_at'); $mapping->text('name', [ + 'analyzer' => 'name_search', 'fields' => [ 'keyword' => [ 'type' => 'keyword', diff --git a/elastic/migrations/2022_10_19_050811_create_playlist_index.php b/elastic/migrations/2022_10_19_050811_create_playlist_index.php index 767966434..71096ee87 100644 --- a/elastic/migrations/2022_10_19_050811_create_playlist_index.php +++ b/elastic/migrations/2022_10_19_050811_create_playlist_index.php @@ -2,18 +2,24 @@ declare(strict_types=1); +use App\Concerns\Elastic\ConfiguresTextAnalyzers; use Elastic\Adapter\Indices\Mapping; +use Elastic\Adapter\Indices\Settings; use Elastic\Migrations\Facades\Index; use Elastic\Migrations\MigrationInterface; final class CreatePlaylistIndex implements MigrationInterface { + use ConfiguresTextAnalyzers; + /** * Run the migration. */ public function up(): void { - Index::createIfNotExists('playlists', function (Mapping $mapping) { + Index::createIfNotExists('playlists', function (Mapping $mapping, Settings $settings) { + $this->configureTextAnalyzers($settings); + $mapping->long('playlist_id'); $mapping->date('created_at'); $mapping->text('hashids', [ @@ -24,6 +30,7 @@ final class CreatePlaylistIndex implements MigrationInterface ], ]); $mapping->text('name', [ + 'analyzer' => 'name_search', 'fields' => [ 'keyword' => [ 'type' => 'keyword', diff --git a/elastic/migrations/2025_05_14_001128_create_profile_index.php b/elastic/migrations/2025_05_14_001128_create_profile_index.php index fb246a44a..566b294d8 100644 --- a/elastic/migrations/2025_05_14_001128_create_profile_index.php +++ b/elastic/migrations/2025_05_14_001128_create_profile_index.php @@ -2,21 +2,28 @@ declare(strict_types=1); +use App\Concerns\Elastic\ConfiguresTextAnalyzers; use Elastic\Adapter\Indices\Mapping; +use Elastic\Adapter\Indices\Settings; use Elastic\Migrations\Facades\Index; use Elastic\Migrations\MigrationInterface; final class CreateProfileIndex implements MigrationInterface { + use ConfiguresTextAnalyzers; + /** * Run the migration. */ public function up(): void { - Index::createIfNotExists('profiles', function (Mapping $mapping) { + Index::createIfNotExists('profiles', function (Mapping $mapping, Settings $settings) { + $this->configureTextAnalyzers($settings); + $mapping->long('profile_id'); $mapping->date('created_at'); $mapping->text('name', [ + 'analyzer' => 'name_search', 'fields' => [ 'keyword' => [ 'type' => 'keyword', diff --git a/lang/en/filament.php b/lang/en/filament.php index d4f65861e..00640f7d8 100644 --- a/lang/en/filament.php +++ b/lang/en/filament.php @@ -1002,6 +1002,9 @@ return [ 'help' => 'Set if this video has subtitles of dialogue.', 'name' => 'Subbed', ], + 'tags' => [ + 'name' => 'Tags', + ], 'uncen' => [ 'help' => 'Set if this video is an uncensored version of a censored sequence.', 'name' => 'Uncensored', diff --git a/tests/Feature/Http/Api/List/External/ExternalProfileStoreTest.php b/tests/Feature/Http/Api/List/External/ExternalProfileStoreTest.php index 9af40cfde..626f2c5ff 100644 --- a/tests/Feature/Http/Api/List/External/ExternalProfileStoreTest.php +++ b/tests/Feature/Http/Api/List/External/ExternalProfileStoreTest.php @@ -230,35 +230,3 @@ test('created if open ai fails', function () { $response->assertCreated(); }); - -test('validation error when flagged by open ai', function () { - Feature::activate(AllowExternalProfileManagement::class); - Config::set(ValidationConstants::MODERATION_SERVICE_QUALIFIED, ModerationService::OPENAI->value); - - Http::fake([ - 'https://api.openai.com/v1/moderations' => Http::response([ - 'results' => [ - 0 => [ - 'flagged' => true, - ], - ], - ]), - ]); - - $visibility = Arr::random(ExternalProfileVisibility::cases()); - - $parameters = array_merge( - ExternalProfile::factory()->raw(), - [ExternalProfile::ATTRIBUTE_VISIBILITY => $visibility->localize()], - ); - - $user = User::factory()->withPermissions(CrudPermission::CREATE->format(ExternalProfile::class))->createOne(); - - Sanctum::actingAs($user); - - $response = post(route('api.externalprofile.store', $parameters)); - - $response->assertJsonValidationErrors([ - ExternalProfile::ATTRIBUTE_NAME, - ]); -});