From a77f3c5fa44d41507356cac3e927d6e49dc01530 Mon Sep 17 00:00:00 2001 From: Kyrch Date: Sun, 29 Oct 2023 18:19:37 -0300 Subject: [PATCH] feat: new media_format attribute to anime (#591) --- .../Wiki/Video/Audio/BackfillAudioAction.php | 2 + app/Enums/Models/Wiki/AnimeMediaFormat.php | 23 ++++++ .../Wiki/Anime/AnimeMediaFormatField.php | 59 ++++++++++++++ app/Http/Api/Schema/Wiki/AnimeSchema.php | 2 + app/Models/Wiki/Anime.php | 5 ++ app/Nova/Lenses/Anime/AnimeLens.php | 8 ++ app/Nova/Resources/Wiki/Anime.php | 11 +++ app/Nova/Resources/Wiki/Anime/Theme.php | 4 +- database/factories/Wiki/AnimeFactory.php | 3 + ...46_add_media_format_attribute_to_anime.php | 29 +++++++ database/seeders/Wiki/AnimeFormatSeeder.php | 78 +++++++++++++++++++ .../2020_12_21_225415_create_anime_index.php | 1 + lang/en/enums.php | 10 +++ lang/en/nova.php | 6 +- 14 files changed, 238 insertions(+), 3 deletions(-) create mode 100644 app/Enums/Models/Wiki/AnimeMediaFormat.php create mode 100644 app/Http/Api/Field/Wiki/Anime/AnimeMediaFormatField.php create mode 100644 database/migrations/2023_10_11_221246_add_media_format_attribute_to_anime.php create mode 100644 database/seeders/Wiki/AnimeFormatSeeder.php diff --git a/app/Actions/Models/Wiki/Video/Audio/BackfillAudioAction.php b/app/Actions/Models/Wiki/Video/Audio/BackfillAudioAction.php index dff1bc641..465332221 100644 --- a/app/Actions/Models/Wiki/Video/Audio/BackfillAudioAction.php +++ b/app/Actions/Models/Wiki/Video/Audio/BackfillAudioAction.php @@ -213,8 +213,10 @@ protected function getAdjacentVideos(): Collection $orderByNameQuery = $sortRelation->getRelationExistenceQuery($sortRelation->getQuery(), $builder, [Anime::ATTRIBUTE_NAME]); $orderBySeasonQuery = $sortRelation->getRelationExistenceQuery($sortRelation->getQuery(), $builder, [Anime::ATTRIBUTE_SEASON]); $orderByYearQuery = $sortRelation->getRelationExistenceQuery($sortRelation->getQuery(), $builder, [Anime::ATTRIBUTE_YEAR]); + $orderByMediaFormatQuery = $sortRelation->getRelationExistenceQuery($sortRelation->getQuery(), $builder, [Anime::ATTRIBUTE_MEDIA_FORMAT]); return $builder->whereHas(AnimeTheme::RELATION_VIDEOS, fn (Builder $relationBuilder) => $relationBuilder->whereKey($this->getModel())) + ->orderBy($orderByMediaFormatQuery->toBase()) ->orderBy($orderByYearQuery->toBase()) ->orderBy($orderBySeasonQuery->toBase()) ->orderBy($orderByNameQuery->toBase()) diff --git a/app/Enums/Models/Wiki/AnimeMediaFormat.php b/app/Enums/Models/Wiki/AnimeMediaFormat.php new file mode 100644 index 000000000..136c8f738 --- /dev/null +++ b/app/Enums/Models/Wiki/AnimeMediaFormat.php @@ -0,0 +1,23 @@ + $studios * @property string|null $synopsis * @property int|null $year + * @property AnimeMediaFormat|null $media_format * * @method static AnimeFactory factory(...$parameters) */ @@ -60,6 +62,7 @@ class Anime extends BaseModel final public const ATTRIBUTE_SLUG = 'slug'; final public const ATTRIBUTE_SYNOPSIS = 'synopsis'; final public const ATTRIBUTE_YEAR = 'year'; + final public const ATTRIBUTE_MEDIA_FORMAT = 'media_format'; final public const RELATION_ARTISTS = 'animethemes.song.artists'; final public const RELATION_AUDIO = 'animethemes.animethemeentries.videos.audio'; @@ -85,6 +88,7 @@ class Anime extends BaseModel Anime::ATTRIBUTE_SLUG, Anime::ATTRIBUTE_SYNOPSIS, Anime::ATTRIBUTE_YEAR, + Anime::ATTRIBUTE_MEDIA_FORMAT ]; /** @@ -160,6 +164,7 @@ public function getRouteKeyName(): string protected $casts = [ Anime::ATTRIBUTE_SEASON => AnimeSeason::class, Anime::ATTRIBUTE_YEAR => 'int', + Anime::ATTRIBUTE_MEDIA_FORMAT => AnimeMediaFormat::class ]; /** diff --git a/app/Nova/Lenses/Anime/AnimeLens.php b/app/Nova/Lenses/Anime/AnimeLens.php index 8b69bf23d..5a55a5f7d 100644 --- a/app/Nova/Lenses/Anime/AnimeLens.php +++ b/app/Nova/Lenses/Anime/AnimeLens.php @@ -4,6 +4,7 @@ namespace App\Nova\Lenses\Anime; +use App\Enums\Models\Wiki\AnimeMediaFormat; use App\Enums\Models\Wiki\AnimeSeason; use App\Models\BaseModel; use App\Models\Wiki\Anime; @@ -61,6 +62,13 @@ public function fields(NovaRequest $request): array ->showOnPreview() ->filterable(), + Select::make(__('nova.fields.anime.media_format.name'), Anime::ATTRIBUTE_MEDIA_FORMAT) + ->options(AnimeMediaFormat::asSelectArray()) + ->displayUsing(fn (?int $enumValue) => AnimeMediaFormat::tryFrom($enumValue ?? 0)?->localize()) + ->sortable() + ->showOnPreview() + ->filterable(), + Textarea::make(__('nova.fields.anime.synopsis.name'), AnimeModel::ATTRIBUTE_SYNOPSIS) ->onlyOnPreview(), diff --git a/app/Nova/Resources/Wiki/Anime.php b/app/Nova/Resources/Wiki/Anime.php index ab975e7cf..5ed2b43fc 100644 --- a/app/Nova/Resources/Wiki/Anime.php +++ b/app/Nova/Resources/Wiki/Anime.php @@ -4,6 +4,7 @@ namespace App\Nova\Resources\Wiki; +use App\Enums\Models\Wiki\AnimeMediaFormat; use App\Enums\Models\Wiki\AnimeSeason; use App\Enums\Models\Wiki\ImageFacet; use App\Enums\Models\Wiki\ResourceSite; @@ -204,6 +205,16 @@ public function fields(NovaRequest $request): array ->filterable() ->showWhenPeeking(), + Select::make(__('nova.fields.anime.media_format.name'), AnimeModel::ATTRIBUTE_MEDIA_FORMAT) + ->options(AnimeMediaFormat::asSelectArray()) + ->displayUsing(fn (?int $enumValue) => AnimeMediaFormat::tryFrom($enumValue ?? 0)?->localize()) + ->sortable() + ->rules(['required', new Enum(AnimeMediaFormat::class)]) + ->help(__('nova.fields.anime.media_format.help')) + ->showOnPreview() + ->filterable() + ->showWhenPeeking(), + Textarea::make(__('nova.fields.anime.synopsis.name'), AnimeModel::ATTRIBUTE_SYNOPSIS) ->rules('max:65535') ->nullable() diff --git a/app/Nova/Resources/Wiki/Anime/Theme.php b/app/Nova/Resources/Wiki/Anime/Theme.php index b3eb787c5..7ca5398d3 100644 --- a/app/Nova/Resources/Wiki/Anime/Theme.php +++ b/app/Nova/Resources/Wiki/Anime/Theme.php @@ -225,8 +225,8 @@ function (Text $field, NovaRequest $novaRequest, FormData $formData) { $type = ThemeType::tryFrom(intval($formData->offsetGet(AnimeTheme::ATTRIBUTE_TYPE))); $slug = $slug->append($type->name); } - if ($slug->isNotEmpty() && $formData->offsetExists(AnimeTheme::ATTRIBUTE_SEQUENCE)) { - $slug = $slug->append($formData->offsetGet(AnimeTheme::ATTRIBUTE_SEQUENCE)); + if ($slug->isNotEmpty()) { + $slug = $slug->append($formData->offsetGet(AnimeTheme::ATTRIBUTE_SEQUENCE) ?? 1); } $field->value = $slug->__toString(); } diff --git a/database/factories/Wiki/AnimeFactory.php b/database/factories/Wiki/AnimeFactory.php index bdea85a62..0e442f213 100644 --- a/database/factories/Wiki/AnimeFactory.php +++ b/database/factories/Wiki/AnimeFactory.php @@ -4,6 +4,7 @@ namespace Database\Factories\Wiki; +use App\Enums\Models\Wiki\AnimeMediaFormat; use App\Enums\Models\Wiki\AnimeSeason; use App\Models\Wiki\Anime; use App\Models\Wiki\Anime\AnimeSynonym; @@ -44,6 +45,7 @@ class AnimeFactory extends Factory public function definition(): array { $season = Arr::random(AnimeSeason::cases()); + $media_format = Arr::random(AnimeMediaFormat::cases()); return [ Anime::ATTRIBUTE_NAME => fake()->words(3, true), @@ -51,6 +53,7 @@ public function definition(): array Anime::ATTRIBUTE_SLUG => Str::slug(fake()->text(191), '_'), Anime::ATTRIBUTE_SYNOPSIS => fake()->text(), Anime::ATTRIBUTE_YEAR => fake()->numberBetween(1960, intval(date('Y')) + 1), + Anime::ATTRIBUTE_MEDIA_FORMAT => $media_format->value, ]; } diff --git a/database/migrations/2023_10_11_221246_add_media_format_attribute_to_anime.php b/database/migrations/2023_10_11_221246_add_media_format_attribute_to_anime.php new file mode 100644 index 000000000..831015dbc --- /dev/null +++ b/database/migrations/2023_10_11_221246_add_media_format_attribute_to_anime.php @@ -0,0 +1,29 @@ +integer(Anime::ATTRIBUTE_MEDIA_FORMAT)->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table(Anime::TABLE, function (Blueprint $table) { + $table->dropColumn(Anime::ATTRIBUTE_MEDIA_FORMAT); + }); + } +}; diff --git a/database/seeders/Wiki/AnimeFormatSeeder.php b/database/seeders/Wiki/AnimeFormatSeeder.php new file mode 100644 index 000000000..bfd13eba5 --- /dev/null +++ b/database/seeders/Wiki/AnimeFormatSeeder.php @@ -0,0 +1,78 @@ +where(Anime::ATTRIBUTE_MEDIA_FORMAT, 0)->get(); + + foreach ($animes->chunk($chunkSize) as $chunk) { + foreach ($chunk as $anime) { + $resource = $anime->resources()->firstWhere(ExternalResource::ATTRIBUTE_SITE, ResourceSite::ANILIST->value); + + if ($resource instanceof ExternalResource) { + $variables = [ + 'id' => $resource->external_id + ]; + + $response = Http::post('https://graphql.anilist.co', [ + 'query' => $query, + 'variables' => $variables + ]); + + $format = Arr::get($response, 'data.Media.format'); + + if ($format !== null) { + if (in_array($format, ['TV', 'TV_SHORT', 'MOVIE'], true)) { + $formats = [ + 'TV' => AnimeMediaFormat::TV->value, + 'TV_SHORT' => AnimeMediaFormat::TV_SHORT->value, + 'MOVIE' => AnimeMediaFormat::MOVIE->value + ]; + + $anime->update([ + Anime::ATTRIBUTE_MEDIA_FORMAT => $formats[$format] + ]); + echo $format; + echo ' -> '; + echo $anime->name; + echo "\n"; + } else { + echo 'no format include -> '; + echo $anime->name; + echo "\n"; + } + } else { + echo 'format null'; + echo "\n"; + } + } + } + sleep(5); + } + } +} 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 ff702e57d..5b0ce77d3 100644 --- a/elastic/migrations/2020_12_21_225415_create_anime_index.php +++ b/elastic/migrations/2020_12_21_225415_create_anime_index.php @@ -27,6 +27,7 @@ public function up(): void ], ]); $mapping->long('season'); + $mapping->long('media_format'); $mapping->text('slug', [ 'fields' => [ 'keyword' => [ diff --git a/lang/en/enums.php b/lang/en/enums.php index ec0fb17fb..d441dab24 100644 --- a/lang/en/enums.php +++ b/lang/en/enums.php @@ -5,6 +5,7 @@ use App\Enums\Models\Billing\BalanceFrequency; use App\Enums\Models\Billing\Service; use App\Enums\Models\List\PlaylistVisibility; +use App\Enums\Models\Wiki\AnimeMediaFormat; use App\Enums\Models\Wiki\AnimeSeason; use App\Enums\Models\Wiki\ImageFacet; use App\Enums\Models\Wiki\ResourceSite; @@ -13,6 +14,15 @@ use App\Enums\Models\Wiki\VideoSource; return [ + AnimeMediaFormat::class => [ + AnimeMediaFormat::UNKNOWN->name => 'Unknown', + AnimeMediaFormat::TV->name => 'TV', + AnimeMediaFormat::TV_SHORT->name => 'TV Short', + AnimeMediaFormat::OVA->name => 'OVA', + AnimeMediaFormat::MOVIE->name => 'Movie', + AnimeMediaFormat::SPECIAL->name => 'Special', + AnimeMediaFormat::ONA->name => 'ONA' + ], AnimeSeason::class => [ AnimeSeason::WINTER->name => 'Winter', AnimeSeason::SPRING->name => 'Spring', diff --git a/lang/en/nova.php b/lang/en/nova.php index 16e89599a..9a915a45e 100644 --- a/lang/en/nova.php +++ b/lang/en/nova.php @@ -393,7 +393,7 @@ 'name' => 'Sequence', ], 'slug' => [ - 'help' => 'Used as the URL Slug / Model Route Key. By default, this should be the Type and Sequence lowercased and "_" replacing spaces. These should be unique within the scope of the anime. Ex: "OP", "ED1", "OP2-Dub".', + 'help' => 'Used as the URL Slug / Model Route Key. By default, this should be the Type and Sequence lowercased and "-" replacing spaces. These should be unique within the scope of the anime. Ex: "OP1", "ED1", "OP2-Dub".', 'name' => 'Slug', ], 'type' => [ @@ -422,6 +422,10 @@ 'help' => 'The Year in which the Anime premiered. By default, we will use the Premiered Field on the MAL page.', 'name' => 'Year', ], + 'media_format' => [ + 'help' => 'The Format of the Anime. By default, we will use the Type Field on the MAL page.', + 'name' => 'Media Format' + ], 'resources' => [ 'as' => [ 'help' => 'Used to distinguish resources that map to the same anime. For example, Aware! Meisaku-kun has one MAL page and many aniDB pages.',