Files
animethemes-server/app/Actions/Models/List/External/SyncExternalProfileAction.php
T
2025-10-01 17:18:31 -03:00

126 lines
4.6 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Actions\Models\List\External;
use App\Actions\Models\List\External\Entry\BaseExternalEntryClaimedAction;
use App\Actions\Models\List\External\Entry\BaseExternalEntryUnclaimedAction;
use App\Enums\Models\List\ExternalProfileSite;
use App\Events\List\ExternalProfile\ExternalProfileSynced;
use App\Models\List\External\ExternalEntry;
use App\Models\List\ExternalProfile;
use App\Models\Wiki\Anime;
use App\Models\Wiki\ExternalResource;
use Exception;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class SyncExternalProfileAction
{
protected Collection $resources;
/**
* @throws Exception
*/
public function handle(ExternalProfile $profile): ExternalProfile
{
try {
DB::beginTransaction();
// A claimed profile will be synced using its external token.
$action = $profile->isClaimed()
? $this->getClaimedActionClass($profile)
: $this->getUnclaimedActionClass($profile);
$entries = $action->getEntries();
$this->cacheResources($profile->site);
$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,
ExternalEntry::ATTRIBUTE_PROFILE => $profile->getKey(),
];
}
}
// Insert the external entries or update its status filtering by anime id.
ExternalEntry::query()
->upsert(
$externalEntries,
[ExternalEntry::ATTRIBUTE_ANIME, ExternalEntry::ATTRIBUTE_PROFILE],
ExternalEntry::fieldsForUpdate(),
);
// An external entry that is not in the external list anymore should be removed.
ExternalEntry::query()
->where(ExternalEntry::ATTRIBUTE_PROFILE, $profile->getKey())
->whereNotIn(ExternalEntry::ATTRIBUTE_ANIME, Arr::map($externalEntries, fn ($value) => $value[ExternalEntry::ATTRIBUTE_ANIME]))
->delete();
$profile->update([ExternalProfile::ATTRIBUTE_SYNCED_AT => now()]);
DB::commit();
ExternalProfileSynced::dispatch($profile);
return $profile;
} catch (Exception $e) {
Log::error($e->getMessage());
DB::rollBack();
throw $e;
}
}
/**
* Get the mapping for the entries token class.
*/
protected function getClaimedActionClass(ExternalProfile $profile): BaseExternalEntryClaimedAction
{
return StoreExternalProfileClaimedAction::getActionClass($profile->site, $profile->externaltoken);
}
/**
* Get the mapping for the entries class.
*/
protected function getUnclaimedActionClass(ExternalProfile $profile): BaseExternalEntryUnclaimedAction
{
return StoreExternalProfileUnclaimedAction::getActionClass($profile->site, $profile);
}
/**
* Cache the resources for performance proposals.
*/
protected function cacheResources(ExternalProfileSite $profileSite): void
{
// External resources are only added by mods so it doesn't change too often.
$this->resources = Cache::flexible("resources_{$profileSite->name}", [60, 300], fn () => ExternalResource::query()
->where(ExternalResource::ATTRIBUTE_SITE, $profileSite->getResourceSite()->value)
->with([ExternalResource::RELATION_ANIME => fn ($query) => $query->select([Anime::TABLE.'.'.Anime::ATTRIBUTE_ID])])
->whereHas(ExternalResource::RELATION_ANIME)
->get()
->mapWithKeys(fn (ExternalResource $resource): array => [$resource->external_id => $resource->anime->map(fn (Anime $anime) => $anime->getKey())]));
}
/**
* @return Collection<int, int>
*/
protected function getAnimesByExternalId(int $externalId): Collection
{
return $this->resources->get($externalId) ?? collect();
}
}