Skip to content

Commit

Permalink
feat: added anilist logic for external entry action (#721)
Browse files Browse the repository at this point in the history
  • Loading branch information
Kyrch authored Aug 3, 2024
1 parent 5ccbd04 commit fdd767f
Show file tree
Hide file tree
Showing 18 changed files with 699 additions and 178 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<?php

declare(strict_types=1);

namespace App\Actions\Models\List\ExternalProfile\ExternalEntry;

use App\Actions\Models\List\ExternalProfile\ExternalEntryAction;
use App\Enums\Models\List\ExternalEntryWatchStatus;
use App\Models\List\External\ExternalEntry;
use App\Models\Wiki\ExternalResource;
use Illuminate\Http\Client\RequestException;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;

/**
* Class AnilistExternalEntryAction.
*/
class AnilistExternalEntryAction extends ExternalEntryAction
{
/**
* Get the entries of the response.
*
* @return array
*/
public function getEntries(): array
{
$entries = [];
$response = $this->makeRequest();

if ($response !== null) {
$lists = Arr::get($response, 'data.MediaListCollection.lists');
$lists = Arr::where($lists, fn ($value, $key) => $value['isCustomList'] === false);

foreach ($lists as $list) {
foreach (Arr::get($list, 'entries') as $entry) {
$entries[] = [
ExternalResource::ATTRIBUTE_EXTERNAL_ID => intval(Arr::get($entry, 'media.id')),
ExternalEntry::ATTRIBUTE_SCORE => Arr::get($entry, 'score'),
ExternalEntry::ATTRIBUTE_WATCH_STATUS => ExternalEntryWatchStatus::getAnilistMapping(Arr::get($entry, 'status'))->value,
ExternalEntry::ATTRIBUTE_IS_FAVORITE => false, // TODO
];
}
}
}

return $entries;
}

/**
* Make the request to the external api.
*
* @return array|null
*/
public function makeRequest(): ?array
{
try {
$query = '
query($userName: String) {
MediaListCollection(userName: $userName, type: ANIME) {
lists {
name
status
isCustomList
entries {
status
score
media {
id
}
}
}
}
}
';

$variables = [
'userName' => $this->getUsername(),
];

$response = Http::post('https://graphql.anilist.co', [
'query' => $query,
'variables' => $variables,
])
->throw()
->json();

return $response;

} catch (RequestException $e) {
Log::error($e->getMessage());

throw $e;
}
}
}
68 changes: 68 additions & 0 deletions app/Actions/Models/List/ExternalProfile/ExternalEntryAction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php

declare(strict_types=1);

namespace App\Actions\Models\List\ExternalProfile;

use App\Enums\Models\List\ExternalProfileSite;
use App\Enums\Models\Wiki\ResourceSite;
use Illuminate\Support\Arr;

/**
* Class ExternalEntryAction
*/
abstract class ExternalEntryAction
{
/**
* Create a new action instance.
*
* @param array $profileParameters
*/
public function __construct(protected array $profileParameters)
{
}

/**
* Get the profile site.
*
* @return ExternalProfileSite
*/
public function getProfileSite(): ExternalProfileSite
{
return ExternalProfileSite::fromLocalizedName(Arr::get($this->profileParameters, 'site'));
}

/**
* Get the resource site of the profile site.
*
* @return ResourceSite
*/
protected function getResourceSite(): ResourceSite
{
return $this->getProfileSite()->getResourceSite();
}

/**
* Get the username of the profile.
*
* @return string
*/
public function getUsername(): string
{
return Arr::get($this->profileParameters, 'name');
}

/**
* Get the entries of the response.
*
* @return array
*/
abstract public function getEntries(): array;

/**
* Make the request to the external api.
*
* @return array|null
*/
abstract public function makeRequest(): ?array;
}
143 changes: 88 additions & 55 deletions app/Actions/Models/List/ExternalProfile/StoreExternalProfileAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,19 @@
namespace App\Actions\Models\List\ExternalProfile;

use App\Actions\Http\Api\StoreAction;
use App\Enums\Models\List\ExternalEntryWatchStatus;
use App\Actions\Models\List\ExternalProfile\ExternalEntry\AnilistExternalEntryAction;
use App\Enums\Models\List\ExternalProfileSite;
use App\Enums\Models\List\ExternalProfileVisibility;
use App\Models\List\External\ExternalEntry;
use App\Models\List\ExternalProfile;
use App\Models\Wiki\Anime;
use App\Models\Wiki\ExternalResource;
use Error;
use Exception;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

Expand All @@ -22,6 +26,8 @@
*/
class StoreExternalProfileAction
{
protected Collection $resources;

/**
* Store external profile and its entries.
*
Expand All @@ -36,65 +42,47 @@ public function store(Builder $builder, array $profileParameters): Model
try {
DB::beginTransaction();

// $getEntriesAction = new GetEntries();

// $entries = $getEntriesAction->get($profileParameters);

$entries = [
'name' => 'Kyrch Profile',
'entries' => [
[
ExternalResource::ATTRIBUTE_EXTERNAL_ID => 101573,
ExternalEntry::ATTRIBUTE_SCORE => 10,
ExternalEntry::ATTRIBUTE_IS_FAVORITE => true,
ExternalEntry::ATTRIBUTE_WATCH_STATUS => ExternalEntryWatchStatus::COMPLETED->value,
],
[
ExternalResource::ATTRIBUTE_EXTERNAL_ID => 477,
ExternalEntry::ATTRIBUTE_SCORE => 9.5,
ExternalEntry::ATTRIBUTE_IS_FAVORITE => false,
ExternalEntry::ATTRIBUTE_WATCH_STATUS => ExternalEntryWatchStatus::WATCHING->value,
],
[
ExternalResource::ATTRIBUTE_EXTERNAL_ID => 934,
ExternalEntry::ATTRIBUTE_SCORE => 8,
ExternalEntry::ATTRIBUTE_IS_FAVORITE => false,
ExternalEntry::ATTRIBUTE_WATCH_STATUS => ExternalEntryWatchStatus::PAUSED->value,
],
],
];

$storeProfileAction = new StoreAction();

$profile = $storeProfileAction->store($builder, $profileParameters);

foreach (Arr::get($entries, 'entries') as $entryParameters) {
$storeEntryAction = new StoreAction();

$externalSite = Arr::get($profileParameters, 'site');
$external_id = Arr::get($entryParameters, 'external_id');

$animes = Anime::query()->whereHas(ExternalResource::TABLE, function (Builder $query) use ($externalSite, $external_id) {
$query->where(ExternalResource::ATTRIBUTE_SITE, ExternalProfileSite::getResourceSite($externalSite)->value)
->where(ExternalResource::ATTRIBUTE_EXTERNAL_ID, $external_id);
})->get();

foreach ($animes as $anime) {
if ($anime instanceof Anime) {
$storeEntryAction->store(ExternalEntry::query(), array_merge(
$entryParameters,
[
ExternalEntry::ATTRIBUTE_ANIME => $anime->getKey(),
ExternalEntry::ATTRIBUTE_PROFILE => $profile->getKey(),
]
));
}
$storeAction = new StoreAction();

$profileSite = ExternalProfileSite::fromLocalizedName(Arr::get($profileParameters, 'site'));

$action = $this->getActionClass($profileSite, $profileParameters);

if ($action === null) {
throw new Error("Undefined action for site {$profileSite->localize()}"); // TODO: check if it is working
}

$entries = $action->getEntries();

$this->preloadResources($profileSite, $entries);

$profile = $storeAction->store($builder, [
ExternalProfile::ATTRIBUTE_USER => Arr::get($profileParameters, ExternalProfile::ATTRIBUTE_USER),
ExternalProfile::ATTRIBUTE_NAME => Arr::get($profileParameters, ExternalProfile::ATTRIBUTE_NAME),
ExternalProfile::ATTRIBUTE_SITE => $profileSite->value,
ExternalProfile::ATTRIBUTE_VISIBILITY => ExternalProfileVisibility::fromLocalizedName(Arr::get($profileParameters, ExternalProfile::ATTRIBUTE_VISIBILITY))->value,
]);

$externalEntries = [];
foreach ($entries as $entry) {
$externalId = Arr::get($entry, 'external_id');

foreach ($this->getAnimesByExternalId($externalId) as $anime) {
$externalEntries[] = [
ExternalEntry::ATTRIBUTE_SCORE => Arr::get($entry, ExternalEntry::ATTRIBUTE_SCORE),
ExternalEntry::ATTRIBUTE_IS_FAVORITE => Arr::get($entry, ExternalEntry::ATTRIBUTE_IS_FAVORITE),
ExternalEntry::ATTRIBUTE_WATCH_STATUS => Arr::get($entry, ExternalEntry::ATTRIBUTE_WATCH_STATUS),
ExternalEntry::ATTRIBUTE_ANIME => $anime->getKey(),
ExternalEntry::ATTRIBUTE_PROFILE => $profile->getKey(),
];
}
}

ExternalEntry::insert($externalEntries);

DB::commit();

return $storeProfileAction->cleanup($profile);
return $profile;
} catch (Exception $e) {
Log::error($e->getMessage());

Expand All @@ -103,4 +91,49 @@ public function store(Builder $builder, array $profileParameters): Model
throw $e;
}
}

/**
* Get the mapping for the entries class.
*
* @param ExternalProfileSite $site
* @param array $profileParameters
* @return ExternalEntryAction|null
*/
protected function getActionClass(ExternalProfileSite $site, array $profileParameters): ?ExternalEntryAction
{
return match ($site) {
ExternalProfileSite::ANILIST => new AnilistExternalEntryAction($profileParameters),
default => null,
};
}

/**
* Preload the resources for performance proposals.
*
* @param ExternalProfileSite $profileSite
* @param array $entries
* @return void
*/
protected function preloadResources(ExternalProfileSite $profileSite, array $entries): void
{
$externalResources = ExternalResource::query()
->where(ExternalResource::ATTRIBUTE_SITE, $profileSite->getResourceSite()->value)
->whereIn(ExternalResource::ATTRIBUTE_EXTERNAL_ID, Arr::pluck($entries, 'external_id'))
->with(ExternalResource::RELATION_ANIME)
->get()
->mapWithKeys(fn (ExternalResource $resource) => [$resource->external_id => $resource->anime]);

$this->resources = $externalResources;
}

/**
* Get the animes by the external id.
*
* @param int $externalId
* @return Collection<int, Anime>
*/
protected function getAnimesByExternalId(int $externalId): Collection
{
return $this->resources[$externalId] ?? new Collection();
}
}
Loading

0 comments on commit fdd767f

Please sign in to comment.