mirror of
https://github.com/AnimeThemes/animethemes-server.git
synced 2026-07-11 01:24:46 +02:00
feat: added anime synonyms to series scout search (#772)
This commit is contained in:
@@ -5,7 +5,6 @@ declare(strict_types=1);
|
||||
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 @@ class DiscordVideoNotificationAction
|
||||
->load([
|
||||
'animethemeentries.animetheme.anime.discordthread',
|
||||
'animethemeentries.animetheme.anime.images',
|
||||
'animethemeentries.animetheme.group',
|
||||
Video::RELATION_GROUP,
|
||||
'animethemeentries.animetheme.song.artists',
|
||||
]);
|
||||
|
||||
@@ -44,7 +42,6 @@ class DiscordVideoNotificationAction
|
||||
$anime = $theme->anime;
|
||||
|
||||
if ($anime->discordthread === null) {
|
||||
if ($shouldForce === ShouldForceThread::NO) return;
|
||||
|
||||
$threadAction = new DiscordThreadAction();
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ trait CanCreateExternalResource
|
||||
$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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Enums\Actions\Models\Wiki\Video;
|
||||
|
||||
use App\Concerns\Enums\LocalizesName;
|
||||
|
||||
/**
|
||||
* Enum ShouldForceThread.
|
||||
*/
|
||||
enum ShouldForceThread: int
|
||||
{
|
||||
use LocalizesName;
|
||||
|
||||
case NO = 0;
|
||||
case YES = 1;
|
||||
|
||||
/**
|
||||
* Get the field key to use in the admin panel.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getFieldKey(): string
|
||||
{
|
||||
return 'should-force-thread';
|
||||
}
|
||||
}
|
||||
@@ -292,12 +292,12 @@ enum ResourceSite: int
|
||||
* Get the URL of the site for resources by determined model.
|
||||
*
|
||||
* @param class-string $modelClass
|
||||
* @param int $id
|
||||
* @param int|null $id
|
||||
* @param string|null $slug
|
||||
* @param string|null $type
|
||||
* @return string|null
|
||||
*/
|
||||
public function formatResourceLink(string $modelClass, int $id, ?string $slug = null, ?string $type = null): ?string
|
||||
public function formatResourceLink(string $modelClass, ?int $id = null, ?string $slug = null, ?string $type = null): ?string
|
||||
{
|
||||
if ($modelClass === Anime::class) {
|
||||
return match ($this) {
|
||||
@@ -362,6 +362,19 @@ enum ResourceSite: int
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the resource uses external id in the url.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function usesIdInLink(): bool
|
||||
{
|
||||
return match ($this) {
|
||||
ResourceSite::ANIME_PLANET => false,
|
||||
default => true,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL capture groups of the resource site.
|
||||
*
|
||||
|
||||
@@ -6,7 +6,6 @@ namespace App\Filament\BulkActions\Models\Wiki\Video;
|
||||
|
||||
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 @@ class VideoDiscordNotificationBulkAction extends BaseBulkAction
|
||||
->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)]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use App\Contracts\Http\Api\InteractsWithSchema;
|
||||
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;
|
||||
|
||||
/**
|
||||
|
||||
+1
-1
@@ -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;
|
||||
@@ -14,6 +14,7 @@ use App\Models\BaseModel;
|
||||
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.
|
||||
*
|
||||
|
||||
@@ -126,11 +126,6 @@ class ArtistPolicy extends BasePolicy
|
||||
*/
|
||||
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())
|
||||
|
||||
@@ -27,8 +27,8 @@ abstract class ElasticQuery
|
||||
* - 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 @@ abstract class ElasticQuery
|
||||
/**
|
||||
* 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 @@ abstract class ElasticQuery
|
||||
* 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
|
||||
|
||||
@@ -7,8 +7,6 @@ namespace App\Scout\Elasticsearch\Api\Query\Wiki;
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace Database\Seeders;
|
||||
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 @@ class DatabaseSeeder extends Seeder
|
||||
$this->call(AudioSeeder::class);
|
||||
$this->call(HashidsSeeder::class);
|
||||
$this->call(FeatureSeeder::class);
|
||||
$this->call(ImportModelsSeeder::class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Database\Seeders\Scout;
|
||||
|
||||
use App\Models\List\Playlist;
|
||||
use App\Models\Wiki\Anime;
|
||||
use App\Models\Wiki\Anime\AnimeSynonym;
|
||||
use App\Models\Wiki\Anime\AnimeTheme;
|
||||
use App\Models\Wiki\Anime\Theme\AnimeThemeEntry;
|
||||
use App\Models\Wiki\Artist;
|
||||
use App\Models\Wiki\Series;
|
||||
use App\Models\Wiki\Song;
|
||||
use App\Models\Wiki\Studio;
|
||||
use App\Models\Wiki\Video;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
|
||||
/**
|
||||
* Class ImportModelsSeeder.
|
||||
*/
|
||||
class ImportModelsSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Seed the application's database.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
$driver = Config::get('scout.driver');
|
||||
if (empty($driver)) {
|
||||
$this->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]);
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,61 @@ final class CreateSeriesIndex implements MigrationInterface
|
||||
],
|
||||
]);
|
||||
$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');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -441,10 +441,6 @@ return [
|
||||
'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?',
|
||||
|
||||
Reference in New Issue
Block a user