-
-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
63 changed files
with
1,509 additions
and
5,114 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace App\Actions\Models; | ||
|
||
use App\Actions\ActionResult; | ||
use App\Actions\Models\Wiki\ApiAction; | ||
use App\Concerns\Models\CanCreateExternalResource; | ||
use App\Concerns\Models\CanCreateImageFromUrl; | ||
use App\Enums\Models\Wiki\ImageFacet; | ||
use App\Enums\Models\Wiki\ResourceSite; | ||
use App\Models\BaseModel; | ||
use App\Models\Wiki\ExternalResource; | ||
use App\Models\Wiki\Image; | ||
use Illuminate\Support\Facades\Log; | ||
use Illuminate\Support\Str; | ||
|
||
/** | ||
* Class BackfillWikiAction. | ||
*/ | ||
abstract class BackfillWikiAction | ||
{ | ||
use CanCreateExternalResource; | ||
use CanCreateImageFromUrl; | ||
|
||
final public const RESOURCES = 'resources'; | ||
final public const IMAGES = 'images'; | ||
|
||
/** | ||
* Create a new action instance. | ||
* | ||
* @param BaseModel $model | ||
* @param array $toBackfill | ||
*/ | ||
public function __construct(protected BaseModel $model, protected array $toBackfill) | ||
{ | ||
} | ||
|
||
/** | ||
* Handle the action. | ||
* | ||
* @return ActionResult | ||
*/ | ||
abstract public function handle(): ActionResult; | ||
|
||
/** | ||
* Get the api actions available for the backfill action. | ||
* | ||
* @return array | ||
*/ | ||
abstract protected function getApis(): array; | ||
|
||
/** | ||
* Create the resources given the response. | ||
* | ||
* @param ApiAction $api | ||
* @return void | ||
*/ | ||
protected function forResources(ApiAction $api): void | ||
{ | ||
$toBackfill = $this->toBackfill[self::RESOURCES]; | ||
|
||
foreach ($api->getResources() as $site => $url) { | ||
$site = ResourceSite::from($site); | ||
|
||
if (!in_array($site, $toBackfill)) { | ||
Log::info("Resource {$site->localize()} should not be backfilled for {$this->label()} {$this->getModel()->getName()}"); | ||
continue; | ||
} | ||
|
||
if ($this->getModel()->resources()->getQuery()->where(ExternalResource::ATTRIBUTE_SITE, $site->value)->exists()) { | ||
Log::info("Resource {$site->localize()} already exists for {$this->label()} {$this->getModel()->getName()}"); | ||
continue; | ||
} | ||
|
||
$resource = $this->getOrCreateResource($this->getModel()::class, $site, $url); | ||
|
||
Log::info("Attaching Resource '{$resource->getName()}' to {$this->label()} '{$this->getModel()->getName()}'"); | ||
$this->getModel()->resources()->attach($resource); | ||
|
||
$this->backfilled($site, self::RESOURCES); | ||
} | ||
} | ||
|
||
/** | ||
* Create the images given the response. | ||
* | ||
* @param ApiAction $api | ||
* @return void | ||
*/ | ||
protected function forImages(ApiAction $api): void | ||
{ | ||
$toBackfill = $this->toBackfill[self::IMAGES]; | ||
|
||
foreach ($api->getImages() as $facet => $url) { | ||
$facet = ImageFacet::from($facet); | ||
|
||
if (!in_array($facet, $toBackfill)) { | ||
Log::info("Skipping {$facet->localize()} for {$this->label()} {$this->getModel()->getName()}"); | ||
continue; | ||
} | ||
|
||
if ($this->getModel()->images()->getQuery()->where(Image::ATTRIBUTE_FACET, $facet->value)->exists()) { | ||
Log::info("Image {$facet->localize()} already exists for {$this->label()} {$this->getModel()->getName()}"); | ||
continue; | ||
} | ||
|
||
$image = $this->createImage($url, $facet, $this->getModel()); | ||
|
||
Log::info("Attaching Image '{$image->getName()}' to {$this->label()} '{$this->getModel()->getName()}'"); | ||
$this->getModel()->images()->attach($image); | ||
|
||
$this->backfilled($facet, self::IMAGES); | ||
} | ||
} | ||
|
||
/** | ||
* Remove element already backfilled. | ||
* | ||
* @param mixed $enum | ||
* @param string $scope | ||
* @return void | ||
*/ | ||
protected function backfilled(mixed $enum, string $scope): void | ||
{ | ||
$index = array_search($enum, $this->toBackfill[$scope]); | ||
|
||
if ($index !== false) { | ||
unset($this->toBackfill[$scope][$index]); | ||
} | ||
} | ||
|
||
/** | ||
* Get the model for the action. | ||
* | ||
* @return BaseModel | ||
*/ | ||
abstract protected function getModel(): BaseModel; | ||
|
||
/** | ||
* Get the human-friendly label for the underlying model. | ||
* | ||
* @return string | ||
*/ | ||
protected function label(): string | ||
{ | ||
return Str::headline(class_basename($this->getModel())); | ||
} | ||
} |
173 changes: 173 additions & 0 deletions
173
app/Actions/Models/Wiki/Anime/ApiAction/AnilistAnimeApiAction.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace App\Actions\Models\Wiki\Anime\ApiAction; | ||
|
||
use App\Actions\Models\Wiki\ApiAction; | ||
use App\Enums\Models\Wiki\AnimeSynonymType; | ||
use App\Enums\Models\Wiki\ImageFacet; | ||
use App\Enums\Models\Wiki\ResourceSite; | ||
use App\Models\Wiki\ExternalResource; | ||
use Illuminate\Database\Eloquent\Relations\BelongsToMany; | ||
use Illuminate\Support\Arr; | ||
use Illuminate\Support\Facades\Http; | ||
|
||
/** | ||
* Class AnilistAnimeApiAction. | ||
*/ | ||
class AnilistAnimeApiAction extends ApiAction | ||
{ | ||
/** | ||
* Get the site to backfill. | ||
* | ||
* @return ResourceSite | ||
*/ | ||
public function getSite(): ResourceSite | ||
{ | ||
return ResourceSite::ANILIST; | ||
} | ||
|
||
/** | ||
* Set the response after the request. | ||
* | ||
* @param BelongsToMany<ExternalResource> $resources | ||
* @return static | ||
*/ | ||
public function handle(BelongsToMany $resources): static | ||
{ | ||
$resource = $resources->firstWhere(ExternalResource::ATTRIBUTE_SITE, ResourceSite::ANILIST->value); | ||
|
||
if ($resource instanceof ExternalResource) { | ||
$query = ' | ||
query ($id: Int) { | ||
Media (id: $id, type: ANIME) { | ||
title { | ||
romaji | ||
english | ||
native | ||
} | ||
coverImage { | ||
extraLarge | ||
medium | ||
} | ||
externalLinks { | ||
url | ||
site | ||
language | ||
} | ||
} | ||
} | ||
'; | ||
|
||
$variables = [ | ||
'id' => $resource->external_id, | ||
]; | ||
|
||
$response = Http::post('https://graphql.anilist.co', [ | ||
'query' => $query, | ||
'variables' => $variables, | ||
]) | ||
->throw() | ||
->json(); | ||
|
||
$this->response = $response; | ||
} | ||
|
||
return $this; | ||
} | ||
|
||
/** | ||
* Get the mapped resources. | ||
* | ||
* @return array<int, string> | ||
*/ | ||
public function getResources(): array | ||
{ | ||
$resources = []; | ||
|
||
if ($response = $this->response) { | ||
$links = Arr::get($response, 'data.Media.externalLinks'); | ||
|
||
foreach ($links as $link) { | ||
$url = Arr::get($link, 'url'); | ||
$siteAnilist = Arr::get($link, 'site'); | ||
$language = Arr::get($link, 'language'); | ||
|
||
foreach ($this->getResourcesMapping() as $site => $key) { | ||
if ($siteAnilist === $key) { | ||
if (in_array($siteAnilist, ['Official Site', 'Twitter']) && !in_array($language, ['Japanese', null])) continue; | ||
|
||
$resources[$site] = $url; | ||
} | ||
} | ||
} | ||
} | ||
|
||
return $resources; | ||
} | ||
|
||
/** | ||
* Get the mapped synonyms. | ||
* | ||
* @return array<int|string, string> | ||
*/ | ||
public function getSynonyms(): array | ||
{ | ||
$synonyms = []; | ||
|
||
if ($this->response) { | ||
foreach ($this->getSynonymsMapping() as $type => $key) { | ||
$synonyms[$type] = Arr::get($this->response, $key); | ||
} | ||
} | ||
|
||
return $synonyms; | ||
} | ||
|
||
/** | ||
* Get the available sites to backfill. | ||
* | ||
* @return array | ||
*/ | ||
protected function getResourcesMapping(): array | ||
{ | ||
return [ | ||
ResourceSite::TWITTER->value => 'Twitter', | ||
ResourceSite::OFFICIAL_SITE->value => 'Official Site', | ||
ResourceSite::NETFLIX->value => 'Netflix', | ||
ResourceSite::CRUNCHYROLL->value => 'Crunchyroll', | ||
ResourceSite::HIDIVE->value => 'HIDIVE', | ||
ResourceSite::AMAZON_PRIME_VIDEO->value => 'Amazon Prime Video', | ||
ResourceSite::HULU->value => 'Hulu', | ||
ResourceSite::DISNEY_PLUS->value => 'Disney Plus', | ||
]; | ||
} | ||
|
||
/** | ||
* Get the mapping for the images. | ||
* | ||
* @return array<int, string> | ||
*/ | ||
protected function getImagesMapping(): array | ||
{ | ||
return [ | ||
ImageFacet::COVER_SMALL->value => 'data.Media.coverImage.medium', | ||
ImageFacet::COVER_LARGE->value => 'data.Media.coverImage.extraLarge', | ||
]; | ||
} | ||
|
||
/** | ||
* Get the mapping for the synonyms. | ||
* | ||
* @return array<int, string> | ||
*/ | ||
protected function getSynonymsMapping(): array | ||
{ | ||
return [ | ||
AnimeSynonymType::ENGLISH->value => 'data.Media.title.english', | ||
AnimeSynonymType::NATIVE->value => 'data.Media.title.native', | ||
AnimeSynonymType::OTHER->value => 'data.Media.title.romaji', | ||
]; | ||
} | ||
} |
Oops, something went wrong.