mirror of
https://github.com/jikan-me/jikan-rest.git
synced 2025-02-20 11:23:35 +08:00
added scraper service and rewritten anime controller
This commit is contained in:
parent
a145f18bbd
commit
69d66378be
11
app/Concerns/ScraperCacheTtl.php
Normal file
11
app/Concerns/ScraperCacheTtl.php
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Concerns;
|
||||||
|
|
||||||
|
trait ScraperCacheTtl
|
||||||
|
{
|
||||||
|
protected function cacheTtl(): int
|
||||||
|
{
|
||||||
|
return (int) env('CACHE_DEFAULT_EXPIRE');
|
||||||
|
}
|
||||||
|
}
|
@ -1,186 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Concerns;
|
|
||||||
|
|
||||||
use App\Contracts\Repository;
|
|
||||||
use App\Http\HttpHelper;
|
|
||||||
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
|
||||||
use Illuminate\Database\Query\Builder as QueryBuilder;
|
|
||||||
use Illuminate\Http\Response;
|
|
||||||
use Illuminate\Support\Carbon;
|
|
||||||
use Illuminate\Support\Collection;
|
|
||||||
use Illuminate\Support\Facades\App;
|
|
||||||
use Illuminate\Support\Facades\DB;
|
|
||||||
use Jikan\MyAnimeList\MalClient;
|
|
||||||
use MongoDB\BSON\UTCDateTime;
|
|
||||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
|
||||||
|
|
||||||
trait ScraperResultCache
|
|
||||||
{
|
|
||||||
protected function queryFromScraperCacheByFingerPrint(string $cacheTableName, string $requestFingerPrint, \Closure $getMalDataCallback, ?int $page = null): Collection
|
|
||||||
{
|
|
||||||
$queryable = DB::table($cacheTableName);
|
|
||||||
$results = $this->getScraperCacheByFingerPrint($queryable, $requestFingerPrint);
|
|
||||||
|
|
||||||
if (
|
|
||||||
$results->isEmpty()
|
|
||||||
|| $this->isExpired($cacheTableName, $results)
|
|
||||||
) {
|
|
||||||
$page = $page ?? 1;
|
|
||||||
$data = App::call(function (MalClient $jikan) use ($getMalDataCallback, $page) {
|
|
||||||
return $getMalDataCallback($jikan, $page);
|
|
||||||
});
|
|
||||||
$response = $this->serializeScraperResult($data);
|
|
||||||
$results = $this->updateCacheByFingerPrint($queryable, $requestFingerPrint, $results, $response);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $results;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Repository $repository
|
|
||||||
* @param int $id
|
|
||||||
* @param string $requestFingerPrint
|
|
||||||
* @return Collection
|
|
||||||
* @throws NotFoundHttpException
|
|
||||||
*/
|
|
||||||
protected function queryFromScraperCacheById(Repository $repository, int $id, string $requestFingerPrint): Collection
|
|
||||||
{
|
|
||||||
$results = $repository->getAllByMalId($id);
|
|
||||||
$tableName = $repository->tableName();
|
|
||||||
|
|
||||||
if ($results->isEmpty() || $this->isExpired($tableName, $results)) {
|
|
||||||
$response = $repository->scrape($id);
|
|
||||||
|
|
||||||
if (HttpHelper::hasError($response)) {
|
|
||||||
abort(404, "Resource not found.");
|
|
||||||
}
|
|
||||||
|
|
||||||
$results = $this->updateCacheById($repository, $id, $requestFingerPrint, $results, $response);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($results->isEmpty()) {
|
|
||||||
abort(404, "Resource not found.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return $results;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function serializeScraperResult($data): array
|
|
||||||
{
|
|
||||||
$serializer = app("SerializerV4");
|
|
||||||
return $serializer->toArray($data);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function prepareScraperResponse(string $requestFingerPrint, Collection $results, array $response): array
|
|
||||||
{
|
|
||||||
// If resource doesn't exist, prepare meta
|
|
||||||
if ($results->isEmpty()) {
|
|
||||||
$meta = [
|
|
||||||
'createdAt' => new UTCDateTime(),
|
|
||||||
'request_hash' => $requestFingerPrint
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update `modifiedAt` meta
|
|
||||||
$meta['modifiedAt'] = new UTCDateTime();
|
|
||||||
// join meta data with response
|
|
||||||
return $meta + $response;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function updateCacheById(Repository $repository, int $id, string $requestFingerPrint, Collection $results, array $response): Collection
|
|
||||||
{
|
|
||||||
$response = $this->prepareScraperResponse($requestFingerPrint, $results, $response);
|
|
||||||
|
|
||||||
if ($results->isEmpty()) {
|
|
||||||
$repository->insert($response);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->isExpired($repository->tableName(), $results)) {
|
|
||||||
$repository->queryByMalId($id)->update($response);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $repository->getAllByMalId($id);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function updateCacheByFingerPrint(QueryBuilder $queryable, string $requestFingerPrint, Collection $results, array $response): Collection
|
|
||||||
{
|
|
||||||
$response = $this->prepareScraperResponse($requestFingerPrint, $results, $response);
|
|
||||||
|
|
||||||
// insert cache if resource doesn't exist
|
|
||||||
if ($results->isEmpty()) {
|
|
||||||
$queryable->insert($response);
|
|
||||||
}
|
|
||||||
|
|
||||||
// update cache if resource exists
|
|
||||||
if ($this->isExpired($queryable->from, $results)) {
|
|
||||||
$this->getQueryableByFingerPrint($queryable, $requestFingerPrint)->update($response);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->getScraperCacheByFingerPrint($queryable, $requestFingerPrint);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template T of Response
|
|
||||||
* @param string $requestFingerPrint
|
|
||||||
* @param Collection $results
|
|
||||||
* @param T $response
|
|
||||||
* @return T
|
|
||||||
*/
|
|
||||||
protected function prepareResponse(string $requestFingerPrint, Collection $results, $response)
|
|
||||||
{
|
|
||||||
return $response
|
|
||||||
->header("X-Request-Fingerprint", $requestFingerPrint)
|
|
||||||
->setTtl($this->getTtl())
|
|
||||||
->setExpires(Carbon::createFromTimestamp($this->getExpiry($results)))
|
|
||||||
->setLastModified(Carbon::createFromTimestamp($this->getLastModified($results)));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getQueryableByFingerPrint(QueryBuilder|EloquentBuilder $queryable, string $requestFingerPrint): EloquentBuilder
|
|
||||||
{
|
|
||||||
return $queryable->where("request_hash", $requestFingerPrint);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getScraperCacheByFingerPrint(QueryBuilder|EloquentBuilder $queryable, string $requestFingerPrint): Collection
|
|
||||||
{
|
|
||||||
return $this->getQueryableByFingerPrint($queryable, $requestFingerPrint)->get();
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getLastModified(Collection $results) : ?int
|
|
||||||
{
|
|
||||||
if (is_array($results->first())) {
|
|
||||||
return (int) $results->first()['modifiedAt']->toDateTime()->format('U');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_object($results->first())) {
|
|
||||||
return (int) $results->first()->modifiedAt->toDateTime()->format('U');
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getTtl(): int
|
|
||||||
{
|
|
||||||
return (int) env('CACHE_DEFAULT_EXPIRE');
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getExpiry(Collection $results): int
|
|
||||||
{
|
|
||||||
$modifiedAt = $this->getLastModified($results);
|
|
||||||
$ttl = $this->getTtl();
|
|
||||||
return $modifiedAt !== null ? $ttl + $modifiedAt : $ttl;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function isExpired(string $cacheTableName, Collection $results): bool
|
|
||||||
{
|
|
||||||
$lastModified = $this->getLastModified($results);
|
|
||||||
|
|
||||||
if ($lastModified === null) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
$expiry = $this->getExpiry($results);
|
|
||||||
|
|
||||||
return time() > $expiry;
|
|
||||||
}
|
|
||||||
}
|
|
38
app/Contracts/CachedScraperService.php
Normal file
38
app/Contracts/CachedScraperService.php
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Contracts;
|
||||||
|
|
||||||
|
use App\Support\CachedData;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Response;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Representation of a service which knows about cached MAL scraper results.
|
||||||
|
*/
|
||||||
|
interface CachedScraperService
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Finds cached scraper results by cacheKey, if not found scrapes them from MAL via the provided callback.
|
||||||
|
* @param string $cacheKey
|
||||||
|
* @param \Closure $getMalDataCallback
|
||||||
|
* @param int|null $page
|
||||||
|
* @return CachedData
|
||||||
|
*/
|
||||||
|
public function findList(string $cacheKey, \Closure $getMalDataCallback, ?int $page = null): CachedData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds cached scraper results by id in the database, if not found scrapes them from MAL.
|
||||||
|
* @param int $id
|
||||||
|
* @param string $cacheKey
|
||||||
|
* @return CachedData
|
||||||
|
* @throws NotFoundHttpException
|
||||||
|
*/
|
||||||
|
public function find(int $id, string $cacheKey): CachedData;
|
||||||
|
|
||||||
|
public function findByKey(string $key, mixed $val, string $cacheKey): CachedData;
|
||||||
|
|
||||||
|
public function get(string $cacheKey): CachedData;
|
||||||
|
|
||||||
|
public function augmentResponse(JsonResponse|Response $response, string $cacheKey, CachedData $scraperResults): JsonResponse|Response;
|
||||||
|
}
|
@ -2,11 +2,6 @@
|
|||||||
|
|
||||||
namespace App\Contracts;
|
namespace App\Contracts;
|
||||||
|
|
||||||
use Illuminate\Http\Resources\Json\JsonResource;
|
|
||||||
use Illuminate\Http\Resources\Json\ResourceCollection;
|
|
||||||
use Illuminate\Http\Response;
|
|
||||||
use Spatie\LaravelData\Data;
|
|
||||||
|
|
||||||
interface Mediator
|
interface Mediator
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
namespace App\Contracts;
|
namespace App\Contracts;
|
||||||
|
|
||||||
use App\JikanApiModel;
|
use App\JikanApiModel;
|
||||||
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
use Illuminate\Contracts\Database\Query\Builder;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -24,13 +24,13 @@ interface Repository extends RepositoryQuery
|
|||||||
|
|
||||||
public function getAllByMalId(int $id): Collection;
|
public function getAllByMalId(int $id): Collection;
|
||||||
|
|
||||||
public function queryByMalId(int $id): EloquentBuilder;
|
public function queryByMalId(int $id): Builder;
|
||||||
|
|
||||||
public function tableName(): string;
|
public function tableName(): string;
|
||||||
|
|
||||||
// fixme: this should not be here.
|
// fixme: this should not be here.
|
||||||
// this is here because we have the "scrape" static method on models
|
// this is here because we have the "scrape" static method on models
|
||||||
public function scrape(int $id): array;
|
public function scrape(int|string $id): array;
|
||||||
|
|
||||||
public function insert(array $attributes): bool;
|
public function insert(array $attributes): bool;
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
namespace App\Contracts;
|
namespace App\Contracts;
|
||||||
|
|
||||||
use App\JikanApiModel;
|
use App\JikanApiModel;
|
||||||
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
use Illuminate\Contracts\Database\Query\Builder;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Laravel\Scout\Builder as ScoutBuilder;
|
use Laravel\Scout\Builder as ScoutBuilder;
|
||||||
|
|
||||||
@ -14,9 +14,9 @@ interface RepositoryQuery
|
|||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @param Collection $params
|
* @param Collection $params
|
||||||
* @return EloquentBuilder<T>|ScoutBuilder<T>
|
* @return Builder<T>|ScoutBuilder<T>
|
||||||
*/
|
*/
|
||||||
public function filter(Collection $params): EloquentBuilder|ScoutBuilder;
|
public function filter(Collection $params): Builder|ScoutBuilder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $keywords
|
* @param string $keywords
|
||||||
@ -24,4 +24,12 @@ interface RepositoryQuery
|
|||||||
* @return ScoutBuilder<T>
|
* @return ScoutBuilder<T>
|
||||||
*/
|
*/
|
||||||
public function search(string $keywords, ?\Closure $callback = null): ScoutBuilder;
|
public function search(string $keywords, ?\Closure $callback = null): ScoutBuilder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a where filter query
|
||||||
|
* @param string $key
|
||||||
|
* @param mixed $value
|
||||||
|
* @return Builder
|
||||||
|
*/
|
||||||
|
public function where(string $key, mixed $value): Builder;
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
namespace App\Contracts;
|
namespace App\Contracts;
|
||||||
|
|
||||||
|
use App\Repositories\DocumentRepository;
|
||||||
|
|
||||||
interface UnitOfWork
|
interface UnitOfWork
|
||||||
{
|
{
|
||||||
public function anime(): AnimeRepository;
|
public function anime(): AnimeRepository;
|
||||||
@ -23,4 +25,11 @@ interface UnitOfWork
|
|||||||
public function animeGenres(): GenreRepository;
|
public function animeGenres(): GenreRepository;
|
||||||
|
|
||||||
public function mangaGenres(): GenreRepository;
|
public function mangaGenres(): GenreRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the repository instance for a document collection which doesn't have a model representation.
|
||||||
|
* @param string $tableName
|
||||||
|
* @return DocumentRepository
|
||||||
|
*/
|
||||||
|
public function documents(string $tableName): DocumentRepository;
|
||||||
}
|
}
|
||||||
|
12
app/Dto/AnimeCharactersLookupCommand.php
Normal file
12
app/Dto/AnimeCharactersLookupCommand.php
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Dto;
|
||||||
|
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends LookupDataCommand<JsonResponse>
|
||||||
|
*/
|
||||||
|
final class AnimeCharactersLookupCommand extends LookupDataCommand
|
||||||
|
{
|
||||||
|
}
|
29
app/Dto/AnimeEpisodeLookupCommand.php
Normal file
29
app/Dto/AnimeEpisodeLookupCommand.php
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Dto;
|
||||||
|
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Spatie\LaravelData\Attributes\Validation\Numeric;
|
||||||
|
use Spatie\LaravelData\Attributes\Validation\Required;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends LookupDataCommand<JsonResponse>
|
||||||
|
*/
|
||||||
|
final class AnimeEpisodeLookupCommand extends LookupDataCommand
|
||||||
|
{
|
||||||
|
#[Numeric, Required]
|
||||||
|
public int $episodeId;
|
||||||
|
|
||||||
|
/** @noinspection PhpUnused */
|
||||||
|
public static function fromMultiple(Request $request, int $id, int $episodeId): ?self
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var AnimeEpisodeLookupCommand $data
|
||||||
|
*/
|
||||||
|
$data = self::fromRequestAndKey($request, $id);
|
||||||
|
$data->episodeId = $episodeId;
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
}
|
16
app/Dto/AnimeEpisodesLookupCommand.php
Normal file
16
app/Dto/AnimeEpisodesLookupCommand.php
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Dto;
|
||||||
|
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Spatie\LaravelData\Attributes\Validation\Numeric;
|
||||||
|
use Spatie\LaravelData\Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends LookupDataCommand<JsonResponse>
|
||||||
|
*/
|
||||||
|
final class AnimeEpisodesLookupCommand extends LookupDataCommand
|
||||||
|
{
|
||||||
|
#[Numeric]
|
||||||
|
public int|Optional $page;
|
||||||
|
}
|
12
app/Dto/AnimeExternalLookupCommand.php
Normal file
12
app/Dto/AnimeExternalLookupCommand.php
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Dto;
|
||||||
|
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends LookupDataCommand<JsonResponse>
|
||||||
|
*/
|
||||||
|
final class AnimeExternalLookupCommand extends LookupDataCommand
|
||||||
|
{
|
||||||
|
}
|
26
app/Dto/AnimeForumLookupCommand.php
Normal file
26
app/Dto/AnimeForumLookupCommand.php
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Dto;
|
||||||
|
|
||||||
|
use App\Casts\EnumCast;
|
||||||
|
use App\Enums\AnimeForumFilterEnum;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Support\Optional;
|
||||||
|
use Spatie\Enum\Laravel\Rules\EnumRule;
|
||||||
|
use Spatie\LaravelData\Attributes\WithCast;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends LookupDataCommand<JsonResponse>
|
||||||
|
*/
|
||||||
|
final class AnimeForumLookupCommand extends LookupDataCommand
|
||||||
|
{
|
||||||
|
#[WithCast(EnumCast::class, AnimeForumFilterEnum::class)]
|
||||||
|
public AnimeForumFilterEnum|Optional $filter;
|
||||||
|
|
||||||
|
public static function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
"filter" => [new EnumRule(AnimeForumFilterEnum::class)]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
12
app/Dto/AnimeFullLookupCommand.php
Normal file
12
app/Dto/AnimeFullLookupCommand.php
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Dto;
|
||||||
|
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends LookupDataCommand<JsonResponse>
|
||||||
|
*/
|
||||||
|
final class AnimeFullLookupCommand extends LookupDataCommand
|
||||||
|
{
|
||||||
|
}
|
12
app/Dto/AnimeLookupCommand.php
Normal file
12
app/Dto/AnimeLookupCommand.php
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Dto;
|
||||||
|
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends LookupDataCommand<JsonResponse>
|
||||||
|
*/
|
||||||
|
final class AnimeLookupCommand extends LookupDataCommand
|
||||||
|
{
|
||||||
|
}
|
12
app/Dto/AnimeMoreInfoLookupCommand.php
Normal file
12
app/Dto/AnimeMoreInfoLookupCommand.php
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Dto;
|
||||||
|
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends LookupDataCommand<JsonResponse>
|
||||||
|
*/
|
||||||
|
final class AnimeMoreInfoLookupCommand extends LookupDataCommand
|
||||||
|
{
|
||||||
|
}
|
16
app/Dto/AnimeNewsLookupCommand.php
Normal file
16
app/Dto/AnimeNewsLookupCommand.php
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Dto;
|
||||||
|
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Spatie\LaravelData\Attributes\Validation\Numeric;
|
||||||
|
use Spatie\LaravelData\Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends LookupDataCommand<JsonResponse>
|
||||||
|
*/
|
||||||
|
final class AnimeNewsLookupCommand extends LookupDataCommand
|
||||||
|
{
|
||||||
|
#[Numeric]
|
||||||
|
public int|Optional $page;
|
||||||
|
}
|
12
app/Dto/AnimePicturesLookupCommand.php
Normal file
12
app/Dto/AnimePicturesLookupCommand.php
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Dto;
|
||||||
|
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends LookupDataCommand<JsonResponse>
|
||||||
|
*/
|
||||||
|
final class AnimePicturesLookupCommand extends LookupDataCommand
|
||||||
|
{
|
||||||
|
}
|
12
app/Dto/AnimeRecommendationsLookupCommand.php
Normal file
12
app/Dto/AnimeRecommendationsLookupCommand.php
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Dto;
|
||||||
|
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends LookupDataCommand<JsonResponse>
|
||||||
|
*/
|
||||||
|
final class AnimeRecommendationsLookupCommand extends LookupDataCommand
|
||||||
|
{
|
||||||
|
}
|
12
app/Dto/AnimeRelationsLookupCommand.php
Normal file
12
app/Dto/AnimeRelationsLookupCommand.php
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Dto;
|
||||||
|
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends LookupDataCommand<JsonResponse>
|
||||||
|
*/
|
||||||
|
final class AnimeRelationsLookupCommand extends LookupDataCommand
|
||||||
|
{
|
||||||
|
}
|
37
app/Dto/AnimeReviewsLookupCommand.php
Normal file
37
app/Dto/AnimeReviewsLookupCommand.php
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Dto;
|
||||||
|
|
||||||
|
use App\Casts\EnumCast;
|
||||||
|
use App\Enums\AnimeReviewsSortEnum;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Spatie\Enum\Laravel\Rules\EnumRule;
|
||||||
|
use Spatie\LaravelData\Attributes\Validation\BooleanType;
|
||||||
|
use Spatie\LaravelData\Attributes\Validation\Numeric;
|
||||||
|
use Spatie\LaravelData\Attributes\WithCast;
|
||||||
|
use Spatie\LaravelData\Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends LookupDataCommand<JsonResponse>
|
||||||
|
*/
|
||||||
|
final class AnimeReviewsLookupCommand extends LookupDataCommand
|
||||||
|
{
|
||||||
|
#[Numeric]
|
||||||
|
public int|Optional $page;
|
||||||
|
|
||||||
|
#[WithCast(EnumCast::class, AnimeReviewsSortEnum::class)]
|
||||||
|
public AnimeReviewsSortEnum|Optional $sort;
|
||||||
|
|
||||||
|
#[BooleanType]
|
||||||
|
public bool|Optional $spoilers;
|
||||||
|
|
||||||
|
#[BooleanType]
|
||||||
|
public bool|Optional $preliminary;
|
||||||
|
|
||||||
|
public static function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
"sort" => [new EnumRule(AnimeReviewsSortEnum::class)]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
12
app/Dto/AnimeStaffLookupCommand.php
Normal file
12
app/Dto/AnimeStaffLookupCommand.php
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Dto;
|
||||||
|
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends LookupDataCommand<JsonResponse>
|
||||||
|
*/
|
||||||
|
final class AnimeStaffLookupCommand extends LookupDataCommand
|
||||||
|
{
|
||||||
|
}
|
12
app/Dto/AnimeStatsLookupCommand.php
Normal file
12
app/Dto/AnimeStatsLookupCommand.php
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Dto;
|
||||||
|
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends LookupDataCommand<JsonResponse>
|
||||||
|
*/
|
||||||
|
final class AnimeStatsLookupCommand extends LookupDataCommand
|
||||||
|
{
|
||||||
|
}
|
12
app/Dto/AnimeStreamingLookupCommand.php
Normal file
12
app/Dto/AnimeStreamingLookupCommand.php
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Dto;
|
||||||
|
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends LookupDataCommand<JsonResponse>
|
||||||
|
*/
|
||||||
|
final class AnimeStreamingLookupCommand extends LookupDataCommand
|
||||||
|
{
|
||||||
|
}
|
12
app/Dto/AnimeThemesLookupCommand.php
Normal file
12
app/Dto/AnimeThemesLookupCommand.php
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Dto;
|
||||||
|
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends LookupDataCommand<JsonResponse>
|
||||||
|
*/
|
||||||
|
final class AnimeThemesLookupCommand extends LookupDataCommand
|
||||||
|
{
|
||||||
|
}
|
16
app/Dto/AnimeUserUpdatesLookupCommand.php
Normal file
16
app/Dto/AnimeUserUpdatesLookupCommand.php
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Dto;
|
||||||
|
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Spatie\LaravelData\Attributes\Validation\Numeric;
|
||||||
|
use Spatie\LaravelData\Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends LookupDataCommand<JsonResponse>
|
||||||
|
*/
|
||||||
|
final class AnimeUserUpdatesLookupCommand extends LookupDataCommand
|
||||||
|
{
|
||||||
|
#[Numeric]
|
||||||
|
public int|Optional $page;
|
||||||
|
}
|
16
app/Dto/AnimeVideosEpisodesLookupCommand.php
Normal file
16
app/Dto/AnimeVideosEpisodesLookupCommand.php
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Dto;
|
||||||
|
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Support\Optional;
|
||||||
|
use Spatie\LaravelData\Attributes\Validation\Numeric;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends LookupDataCommand<JsonResponse>
|
||||||
|
*/
|
||||||
|
final class AnimeVideosEpisodesLookupCommand extends LookupDataCommand
|
||||||
|
{
|
||||||
|
#[Numeric]
|
||||||
|
public int|Optional $page;
|
||||||
|
}
|
12
app/Dto/AnimeVideosLookupCommand.php
Normal file
12
app/Dto/AnimeVideosLookupCommand.php
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Dto;
|
||||||
|
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends LookupDataCommand<JsonResponse>
|
||||||
|
*/
|
||||||
|
final class AnimeVideosLookupCommand extends LookupDataCommand
|
||||||
|
{
|
||||||
|
}
|
34
app/Dto/LookupDataCommand.php
Normal file
34
app/Dto/LookupDataCommand.php
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Dto;
|
||||||
|
|
||||||
|
use App\Concerns\HasRequestFingerprint;
|
||||||
|
use App\Contracts\DataRequest;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||||
|
use Illuminate\Http\Response;
|
||||||
|
use Spatie\LaravelData\Attributes\Validation\Numeric;
|
||||||
|
use Spatie\LaravelData\Attributes\Validation\Required;
|
||||||
|
use Spatie\LaravelData\Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for all requests/commands which are for looking up things by id.
|
||||||
|
* @template T of ResourceCollection|JsonResource|Response
|
||||||
|
* @implements DataRequest<T>
|
||||||
|
*/
|
||||||
|
abstract class LookupDataCommand extends Data implements DataRequest
|
||||||
|
{
|
||||||
|
use HasRequestFingerprint;
|
||||||
|
|
||||||
|
#[Numeric, Required]
|
||||||
|
public int $id;
|
||||||
|
|
||||||
|
/** @noinspection PhpUnused */
|
||||||
|
public static function fromRequestAndKey(Request $request, int $id): self
|
||||||
|
{
|
||||||
|
$data = static::fromRequest($request);
|
||||||
|
$data->id = $id;
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
}
|
@ -1,21 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Dto;
|
|
||||||
|
|
||||||
use App\Concerns\HasRequestFingerprint;
|
|
||||||
use App\Contracts\DataRequest;
|
|
||||||
use Illuminate\Http\JsonResponse;
|
|
||||||
use Spatie\LaravelData\Attributes\Validation\Numeric;
|
|
||||||
use Spatie\LaravelData\Attributes\Validation\Required;
|
|
||||||
use Spatie\LaravelData\Data;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @implements DataRequest<JsonResponse>
|
|
||||||
*/
|
|
||||||
class QueryFullAnimeCommand extends Data implements DataRequest
|
|
||||||
{
|
|
||||||
use HasRequestFingerprint;
|
|
||||||
|
|
||||||
#[Numeric, Required]
|
|
||||||
public int $id;
|
|
||||||
}
|
|
@ -11,6 +11,7 @@ use Spatie\LaravelData\Attributes\Validation\Alpha;
|
|||||||
use Spatie\LaravelData\Attributes\Validation\IntegerType;
|
use Spatie\LaravelData\Attributes\Validation\IntegerType;
|
||||||
use Spatie\LaravelData\Attributes\Validation\Max;
|
use Spatie\LaravelData\Attributes\Validation\Max;
|
||||||
use Spatie\LaravelData\Attributes\Validation\Min;
|
use Spatie\LaravelData\Attributes\Validation\Min;
|
||||||
|
use Spatie\LaravelData\Attributes\Validation\Numeric;
|
||||||
use Spatie\LaravelData\Attributes\Validation\Size;
|
use Spatie\LaravelData\Attributes\Validation\Size;
|
||||||
use Spatie\LaravelData\Attributes\Validation\StringType;
|
use Spatie\LaravelData\Attributes\Validation\StringType;
|
||||||
use Spatie\LaravelData\Attributes\WithCast;
|
use Spatie\LaravelData\Attributes\WithCast;
|
||||||
@ -26,6 +27,9 @@ class SearchCommand extends Data
|
|||||||
#[Max(255), StringType]
|
#[Max(255), StringType]
|
||||||
public string|Optional $q;
|
public string|Optional $q;
|
||||||
|
|
||||||
|
#[Numeric, Min(1)]
|
||||||
|
public int|Optional $page;
|
||||||
|
|
||||||
#[IntegerType, Min(1)]
|
#[IntegerType, Min(1)]
|
||||||
public int|Optional $limit;
|
public int|Optional $limit;
|
||||||
|
|
||||||
|
12
app/Dto/UserByIdLookupCommand.php
Normal file
12
app/Dto/UserByIdLookupCommand.php
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Dto;
|
||||||
|
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends LookupDataCommand<JsonResponse>
|
||||||
|
*/
|
||||||
|
final class UserByIdLookupCommand extends LookupDataCommand
|
||||||
|
{
|
||||||
|
}
|
14
app/Enums/AnimeForumFilterEnum.php
Normal file
14
app/Enums/AnimeForumFilterEnum.php
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
use Spatie\Enum\Laravel\Enum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @method static self all()
|
||||||
|
* @method static self episode()
|
||||||
|
* @method static self other()
|
||||||
|
*/
|
||||||
|
final class AnimeForumFilterEnum extends Enum
|
||||||
|
{
|
||||||
|
}
|
23
app/Enums/AnimeReviewsSortEnum.php
Normal file
23
app/Enums/AnimeReviewsSortEnum.php
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
use Jikan\Helper\Constants;
|
||||||
|
use Spatie\Enum\Laravel\Enum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @method static self mostVoted()
|
||||||
|
* @method static self newest()
|
||||||
|
* @method static self oldest()
|
||||||
|
*/
|
||||||
|
final class AnimeReviewsSortEnum extends Enum
|
||||||
|
{
|
||||||
|
protected static function labels(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
"mostVoted" => Constants::REVIEWS_SORT_MOST_VOTED,
|
||||||
|
"newest" => Constants::REVIEWS_SORT_NEWEST,
|
||||||
|
"oldest" => Constants::REVIEWS_SORT_OLDEST
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
40
app/Features/AnimeCharactersLookupHandler.php
Normal file
40
app/Features/AnimeCharactersLookupHandler.php
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Features;
|
||||||
|
|
||||||
|
use App\Dto\AnimeCharactersLookupCommand;
|
||||||
|
use App\Http\Resources\V4\AnimeCharactersResource;
|
||||||
|
use App\Support\CachedData;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Jikan\MyAnimeList\MalClient;
|
||||||
|
use Jikan\Request\Anime\AnimeCharactersAndStaffRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends RequestHandlerWithScraperCache<AnimeCharactersLookupCommand, JsonResponse>
|
||||||
|
*/
|
||||||
|
final class AnimeCharactersLookupHandler extends RequestHandlerWithScraperCache
|
||||||
|
{
|
||||||
|
protected function resource(Collection $results): JsonResource
|
||||||
|
{
|
||||||
|
return new AnimeCharactersResource($results->first());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function requestClass(): string
|
||||||
|
{
|
||||||
|
return AnimeCharactersLookupCommand::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getScraperData(string $requestFingerPrint, Collection $requestParams): CachedData
|
||||||
|
{
|
||||||
|
$id = $requestParams->get("id");
|
||||||
|
return $this->scraperService->findList(
|
||||||
|
$requestFingerPrint,
|
||||||
|
fn (MalClient $jikan, ?int $page = null) => $jikan->getAnimeCharactersAndStaff(new AnimeCharactersAndStaffRequest($id))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
44
app/Features/AnimeEpisodeLookupHandler.php
Normal file
44
app/Features/AnimeEpisodeLookupHandler.php
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Features;
|
||||||
|
|
||||||
|
use App\Dto\AnimeEpisodeLookupCommand;
|
||||||
|
use App\Http\Resources\V4\AnimeEpisodeResource;
|
||||||
|
use App\Support\CachedData;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Jikan\MyAnimeList\MalClient;
|
||||||
|
use Jikan\Request\Anime\AnimeEpisodeRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends RequestHandlerWithScraperCache<AnimeEpisodeLookupCommand, JsonResponse>
|
||||||
|
*/
|
||||||
|
final class AnimeEpisodeLookupHandler extends RequestHandlerWithScraperCache
|
||||||
|
{
|
||||||
|
protected function resource(Collection $results): JsonResource
|
||||||
|
{
|
||||||
|
return new AnimeEpisodeResource(
|
||||||
|
$results->first()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function requestClass(): string
|
||||||
|
{
|
||||||
|
return AnimeEpisodeLookupCommand::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getScraperData(string $requestFingerPrint, Collection $requestParams): CachedData
|
||||||
|
{
|
||||||
|
$id = $requestParams->get("id");
|
||||||
|
$episodeId = $requestParams->get("episodeId");
|
||||||
|
|
||||||
|
return $this->scraperService->findList(
|
||||||
|
$requestFingerPrint,
|
||||||
|
fn (MalClient $jikan, ?int $page = null) => $jikan->getAnimeEpisode(new AnimeEpisodeRequest($id, $episodeId)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
31
app/Features/AnimeEpisodesLookupHandler.php
Normal file
31
app/Features/AnimeEpisodesLookupHandler.php
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Features;
|
||||||
|
|
||||||
|
use App\Dto\AnimeEpisodesLookupCommand;
|
||||||
|
use App\Support\CachedData;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Jikan\MyAnimeList\MalClient;
|
||||||
|
use Jikan\Request\Anime\AnimeEpisodesRequest;
|
||||||
|
|
||||||
|
final class AnimeEpisodesLookupHandler extends RequestHandlerWithScraperCache
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function requestClass(): string
|
||||||
|
{
|
||||||
|
return AnimeEpisodesLookupCommand::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getScraperData(string $requestFingerPrint, Collection $requestParams): CachedData
|
||||||
|
{
|
||||||
|
$id = $requestParams->get("id");
|
||||||
|
|
||||||
|
return $this->scraperService->findList(
|
||||||
|
$requestFingerPrint,
|
||||||
|
fn (MalClient $jikan, ?int $page = null) => $jikan->getAnimeEpisodes(new AnimeEpisodesRequest($id, $page)),
|
||||||
|
$requestParams->get("page", 1)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
27
app/Features/AnimeExternalLookupHandler.php
Normal file
27
app/Features/AnimeExternalLookupHandler.php
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Features;
|
||||||
|
|
||||||
|
use App\Dto\AnimeExternalLookupCommand;
|
||||||
|
use App\Http\Resources\V4\ExternalLinksResource;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends ItemLookupHandler<AnimeExternalLookupCommand, JsonResponse>
|
||||||
|
*/
|
||||||
|
final class AnimeExternalLookupHandler extends ItemLookupHandler
|
||||||
|
{
|
||||||
|
protected function resource(Collection $results): JsonResource
|
||||||
|
{
|
||||||
|
return new ExternalLinksResource(
|
||||||
|
$results->first()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function requestClass(): string
|
||||||
|
{
|
||||||
|
return AnimeExternalLookupCommand::class;
|
||||||
|
}
|
||||||
|
}
|
47
app/Features/AnimeForumLookupHandler.php
Normal file
47
app/Features/AnimeForumLookupHandler.php
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Features;
|
||||||
|
|
||||||
|
use App\Dto\AnimeForumLookupCommand;
|
||||||
|
use App\Enums\AnimeForumFilterEnum;
|
||||||
|
use App\Http\Resources\V4\ForumResource;
|
||||||
|
use App\Support\CachedData;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Jikan\MyAnimeList\MalClient;
|
||||||
|
use Jikan\Request\Anime\AnimeForumRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends RequestHandlerWithScraperCache<AnimeForumLookupCommand, JsonResponse>
|
||||||
|
*/
|
||||||
|
final class AnimeForumLookupHandler extends RequestHandlerWithScraperCache
|
||||||
|
{
|
||||||
|
protected function resource(Collection $results): JsonResource
|
||||||
|
{
|
||||||
|
return new ForumResource(
|
||||||
|
$results->first()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function requestClass(): string
|
||||||
|
{
|
||||||
|
return AnimeForumLookupCommand::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getScraperData(string $requestFingerPrint, Collection $requestParams): CachedData
|
||||||
|
{
|
||||||
|
$id = $requestParams->get("id");
|
||||||
|
$topic = $requestParams->get("filter", AnimeForumFilterEnum::all()->value);
|
||||||
|
|
||||||
|
return $this->scraperService->findList(
|
||||||
|
$requestFingerPrint,
|
||||||
|
fn (MalClient $jikan, ?int $page = null) => collect(
|
||||||
|
["topics" => $jikan->getAnimeForum(new AnimeForumRequest($id, $topic))]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
45
app/Features/AnimeMoreInfoLookupHandler.php
Normal file
45
app/Features/AnimeMoreInfoLookupHandler.php
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Features;
|
||||||
|
|
||||||
|
use App\Dto\AnimeStatsLookupCommand;
|
||||||
|
use App\Http\Resources\V4\MoreInfoResource;
|
||||||
|
use App\Support\CachedData;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Jikan\MyAnimeList\MalClient;
|
||||||
|
use Jikan\Request\Anime\AnimeMoreInfoRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends RequestHandlerWithScraperCache<AnimeStatsLookupCommand, JsonResponse>
|
||||||
|
*/
|
||||||
|
final class AnimeMoreInfoLookupHandler extends RequestHandlerWithScraperCache
|
||||||
|
{
|
||||||
|
protected function resource(Collection $results): JsonResource
|
||||||
|
{
|
||||||
|
return new MoreInfoResource(
|
||||||
|
$results->first()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function requestClass(): string
|
||||||
|
{
|
||||||
|
return AnimeStatsLookupCommand::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getScraperData(string $requestFingerPrint, Collection $requestParams): CachedData
|
||||||
|
{
|
||||||
|
$id = $requestParams->get("id");
|
||||||
|
|
||||||
|
return $this->scraperService->findList(
|
||||||
|
$requestFingerPrint,
|
||||||
|
fn (MalClient $jikan, ?int $page = null) => collect(
|
||||||
|
["moreinfo" => $jikan->getAnimeMoreInfo(new AnimeMoreInfoRequest($id))]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
35
app/Features/AnimeNewsLookupHandler.php
Normal file
35
app/Features/AnimeNewsLookupHandler.php
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Features;
|
||||||
|
|
||||||
|
use App\Dto\AnimeNewsLookupCommand;
|
||||||
|
use App\Support\CachedData;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Jikan\MyAnimeList\MalClient;
|
||||||
|
use Jikan\Request\Anime\AnimeNewsRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends RequestHandlerWithScraperCache<AnimeNewsLookupCommand, JsonResponse>
|
||||||
|
*/
|
||||||
|
final class AnimeNewsLookupHandler extends RequestHandlerWithScraperCache
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function requestClass(): string
|
||||||
|
{
|
||||||
|
return AnimeNewsLookupCommand::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getScraperData(string $requestFingerPrint, Collection $requestParams): CachedData
|
||||||
|
{
|
||||||
|
$id = $requestParams->get("id");
|
||||||
|
|
||||||
|
return $this->scraperService->findList(
|
||||||
|
$requestFingerPrint,
|
||||||
|
fn (MalClient $jikan, ?int $page = null) => $jikan->getNewsList(new AnimeNewsRequest($id, $page)),
|
||||||
|
$requestParams->get("page", 1)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
46
app/Features/AnimePicturesLookupHandler.php
Normal file
46
app/Features/AnimePicturesLookupHandler.php
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Features;
|
||||||
|
|
||||||
|
use App\Dto\AnimePicturesLookupCommand;
|
||||||
|
use App\Http\Resources\V4\PicturesResource;
|
||||||
|
use App\Support\CachedData;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Jikan\MyAnimeList\MalClient;
|
||||||
|
use Jikan\Request\Anime\AnimePicturesRequest;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends RequestHandlerWithScraperCache<AnimePicturesLookupCommand, JsonResponse>
|
||||||
|
*/
|
||||||
|
final class AnimePicturesLookupHandler extends RequestHandlerWithScraperCache
|
||||||
|
{
|
||||||
|
protected function resource(Collection $results): JsonResource
|
||||||
|
{
|
||||||
|
return new PicturesResource(
|
||||||
|
$results->first()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function requestClass(): string
|
||||||
|
{
|
||||||
|
return AnimePicturesLookupCommand::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getScraperData(string $requestFingerPrint, Collection $requestParams): CachedData
|
||||||
|
{
|
||||||
|
$id = $requestParams->get("id");
|
||||||
|
|
||||||
|
return $this->scraperService->findList(
|
||||||
|
$requestFingerPrint,
|
||||||
|
fn (MalClient $jikan, ?int $page = null) => collect(
|
||||||
|
["pictures" => $jikan->getAnimePictures(new AnimePicturesRequest($id))]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
45
app/Features/AnimeRecommendationsLookupHandler.php
Normal file
45
app/Features/AnimeRecommendationsLookupHandler.php
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Features;
|
||||||
|
|
||||||
|
use App\Dto\AnimeRecommendationsLookupCommand;
|
||||||
|
use App\Http\Resources\V4\RecommendationsResource;
|
||||||
|
use App\Support\CachedData;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Jikan\MyAnimeList\MalClient;
|
||||||
|
use Jikan\Request\Anime\AnimeRecommendationsRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends RequestHandlerWithScraperCache<AnimeRecommendationsLookupCommand, JsonResponse>
|
||||||
|
*/
|
||||||
|
final class AnimeRecommendationsLookupHandler extends RequestHandlerWithScraperCache
|
||||||
|
{
|
||||||
|
protected function resource(Collection $results): JsonResource
|
||||||
|
{
|
||||||
|
return new RecommendationsResource(
|
||||||
|
$results->first()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function requestClass(): string
|
||||||
|
{
|
||||||
|
return AnimeRecommendationsLookupCommand::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getScraperData(string $requestFingerPrint, Collection $requestParams): CachedData
|
||||||
|
{
|
||||||
|
$id = $requestParams->get("id");
|
||||||
|
|
||||||
|
return $this->scraperService->findList(
|
||||||
|
$requestFingerPrint,
|
||||||
|
fn (MalClient $jikan, ?int $page = null) => collect(
|
||||||
|
["recommendations" => $jikan->getAnimeRecommendations(new AnimeRecommendationsRequest($id))]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
30
app/Features/AnimeRelationsLookupHandler.php
Normal file
30
app/Features/AnimeRelationsLookupHandler.php
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Features;
|
||||||
|
|
||||||
|
use App\Dto\AnimeRelationsLookupCommand;
|
||||||
|
use App\Http\Resources\V4\AnimeRelationsResource;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends ItemLookupHandler<AnimeRelationsLookupCommand, JsonResponse>
|
||||||
|
*/
|
||||||
|
final class AnimeRelationsLookupHandler extends ItemLookupHandler
|
||||||
|
{
|
||||||
|
protected function resource(Collection $results): JsonResource
|
||||||
|
{
|
||||||
|
return new AnimeRelationsResource(
|
||||||
|
$results->first()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function requestClass(): string
|
||||||
|
{
|
||||||
|
return AnimeRelationsLookupCommand::class;
|
||||||
|
}
|
||||||
|
}
|
41
app/Features/AnimeReviewsLookupHandler.php
Normal file
41
app/Features/AnimeReviewsLookupHandler.php
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Features;
|
||||||
|
|
||||||
|
use App\Dto\AnimeReviewsLookupCommand;
|
||||||
|
use App\Enums\AnimeReviewsSortEnum;
|
||||||
|
use App\Support\CachedData;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Jikan\MyAnimeList\MalClient;
|
||||||
|
use Jikan\Request\Anime\AnimeReviewsRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends RequestHandlerWithScraperCache<AnimeReviewsLookupCommand, JsonResponse>
|
||||||
|
*/
|
||||||
|
final class AnimeReviewsLookupHandler extends RequestHandlerWithScraperCache
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function requestClass(): string
|
||||||
|
{
|
||||||
|
return AnimeReviewsLookupCommand::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getScraperData(string $requestFingerPrint, Collection $requestParams): CachedData
|
||||||
|
{
|
||||||
|
$id = $requestParams->get("id");
|
||||||
|
$sort = $requestParams->get("sort", AnimeReviewsSortEnum::mostVoted()->value);
|
||||||
|
$spoilers = $requestParams->get("spoilers", false);
|
||||||
|
$preliminary = $requestParams->get("preliminary", false);
|
||||||
|
|
||||||
|
return $this->scraperService->findList(
|
||||||
|
$requestFingerPrint,
|
||||||
|
fn (MalClient $jikan, ?int $page = null) => $jikan->getAnimeReviews(new AnimeReviewsRequest(
|
||||||
|
$id, $page, $sort, $spoilers, $preliminary
|
||||||
|
)),
|
||||||
|
$requestParams->get("page", 1)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
40
app/Features/AnimeStaffLookupHandler.php
Normal file
40
app/Features/AnimeStaffLookupHandler.php
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Features;
|
||||||
|
|
||||||
|
use App\Dto\AnimeStaffLookupCommand;
|
||||||
|
use App\Http\Resources\V4\AnimeStaffResource;
|
||||||
|
use App\Support\CachedData;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Jikan\MyAnimeList\MalClient;
|
||||||
|
use Jikan\Request\Anime\AnimeCharactersAndStaffRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends RequestHandlerWithScraperCache<AnimeStaffLookupCommand, JsonResponse>
|
||||||
|
*/
|
||||||
|
final class AnimeStaffLookupHandler extends RequestHandlerWithScraperCache
|
||||||
|
{
|
||||||
|
protected function resource(Collection $results): JsonResource
|
||||||
|
{
|
||||||
|
return new AnimeStaffResource($results->first());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function requestClass(): string
|
||||||
|
{
|
||||||
|
return AnimeStaffLookupCommand::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getScraperData(string $requestFingerPrint, Collection $requestParams): CachedData
|
||||||
|
{
|
||||||
|
$id = $requestParams->get("id");
|
||||||
|
return $this->scraperService->findList(
|
||||||
|
$requestFingerPrint,
|
||||||
|
fn (MalClient $jikan, ?int $page = null) => $jikan->getAnimeCharactersAndStaff(new AnimeCharactersAndStaffRequest($id))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
42
app/Features/AnimeStatsLookupHandler.php
Normal file
42
app/Features/AnimeStatsLookupHandler.php
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Features;
|
||||||
|
|
||||||
|
use App\Dto\AnimeStatsLookupCommand;
|
||||||
|
use App\Http\Resources\V4\AnimeStatisticsResource;
|
||||||
|
use App\Support\CachedData;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Jikan\MyAnimeList\MalClient;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends RequestHandlerWithScraperCache<AnimeStatsLookupCommand, JsonResponse>
|
||||||
|
*/
|
||||||
|
final class AnimeStatsLookupHandler extends RequestHandlerWithScraperCache
|
||||||
|
{
|
||||||
|
protected function resource(Collection $results): JsonResource
|
||||||
|
{
|
||||||
|
return new AnimeStatisticsResource(
|
||||||
|
$results->first()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function requestClass(): string
|
||||||
|
{
|
||||||
|
return AnimeStatsLookupCommand::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getScraperData(string $requestFingerPrint, Collection $requestParams): CachedData
|
||||||
|
{
|
||||||
|
$id = $requestParams->get("id");
|
||||||
|
|
||||||
|
return $this->scraperService->findList(
|
||||||
|
$requestFingerPrint,
|
||||||
|
fn (MalClient $jikan, ?int $page = null) => $jikan->getAnimeStats($id)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
30
app/Features/AnimeStreamingLookupHandler.php
Normal file
30
app/Features/AnimeStreamingLookupHandler.php
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Features;
|
||||||
|
|
||||||
|
use App\Dto\AnimeStreamingLookupCommand;
|
||||||
|
use App\Http\Resources\V4\StreamingLinksResource;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends ItemLookupHandler<AnimeStreamingLookupCommand, JsonResponse>
|
||||||
|
*/
|
||||||
|
final class AnimeStreamingLookupHandler extends ItemLookupHandler
|
||||||
|
{
|
||||||
|
protected function resource(Collection $results): JsonResource
|
||||||
|
{
|
||||||
|
return new StreamingLinksResource(
|
||||||
|
$results->first()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function requestClass(): string
|
||||||
|
{
|
||||||
|
return AnimeStreamingLookupCommand::class;
|
||||||
|
}
|
||||||
|
}
|
31
app/Features/AnimeThemesLookupHandler.php
Normal file
31
app/Features/AnimeThemesLookupHandler.php
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Features;
|
||||||
|
|
||||||
|
use App\Dto\AnimeThemesLookupCommand;
|
||||||
|
use App\Http\Resources\V4\StreamingLinksResource;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends ItemLookupHandler<AnimeThemesLookupCommand, JsonResponse>
|
||||||
|
*/
|
||||||
|
final class AnimeThemesLookupHandler extends ItemLookupHandler
|
||||||
|
{
|
||||||
|
|
||||||
|
protected function resource(Collection $results): JsonResource
|
||||||
|
{
|
||||||
|
return new StreamingLinksResource(
|
||||||
|
$results->first()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function requestClass(): string
|
||||||
|
{
|
||||||
|
return AnimeThemesLookupCommand::class;
|
||||||
|
}
|
||||||
|
}
|
37
app/Features/AnimeUserUpdatesLookupHandler.php
Normal file
37
app/Features/AnimeUserUpdatesLookupHandler.php
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Features;
|
||||||
|
|
||||||
|
use App\Dto\AnimeUserUpdatesLookupCommand;
|
||||||
|
use App\Support\CachedData;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Jikan\MyAnimeList\MalClient;
|
||||||
|
use Jikan\Request\Anime\AnimeRecentlyUpdatedByUsersRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends RequestHandlerWithScraperCache<AnimeUserUpdatesLookupCommand, JsonResponse>
|
||||||
|
*/
|
||||||
|
final class AnimeUserUpdatesLookupHandler extends RequestHandlerWithScraperCache
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function requestClass(): string
|
||||||
|
{
|
||||||
|
return AnimeUserUpdatesLookupCommand::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getScraperData(string $requestFingerPrint, Collection $requestParams): CachedData
|
||||||
|
{
|
||||||
|
$id = $requestParams->get("id");
|
||||||
|
|
||||||
|
return $this->scraperService->findList(
|
||||||
|
$requestFingerPrint,
|
||||||
|
fn (MalClient $jikan, ?int $page = null) => $jikan->getAnimeRecentlyUpdatedByUsers(
|
||||||
|
new AnimeRecentlyUpdatedByUsersRequest($id, $page)
|
||||||
|
),
|
||||||
|
$requestParams->get("page", 1)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
45
app/Features/AnimeVideosEpisodesLookupHandler.php
Normal file
45
app/Features/AnimeVideosEpisodesLookupHandler.php
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Features;
|
||||||
|
|
||||||
|
use App\Dto\AnimeVideosEpisodesLookupCommand;
|
||||||
|
use App\Http\Resources\V4\AnimeEpisodesResource;
|
||||||
|
use App\Support\CachedData;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Jikan\MyAnimeList\MalClient;
|
||||||
|
use Jikan\Request\Anime\AnimeVideosEpisodesRequest;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends RequestHandlerWithScraperCache<AnimeVideosEpisodesLookupCommand, JsonResponse>
|
||||||
|
*/
|
||||||
|
final class AnimeVideosEpisodesLookupHandler extends RequestHandlerWithScraperCache
|
||||||
|
{
|
||||||
|
protected function resource(Collection $results): JsonResource
|
||||||
|
{
|
||||||
|
return new AnimeEpisodesResource(
|
||||||
|
$results->first()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function requestClass(): string
|
||||||
|
{
|
||||||
|
return AnimeVideosEpisodesLookupCommand::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getScraperData(string $requestFingerPrint, Collection $requestParams): CachedData
|
||||||
|
{
|
||||||
|
$id = $requestParams->get("id");
|
||||||
|
|
||||||
|
return $this->scraperService->findList(
|
||||||
|
$requestFingerPrint,
|
||||||
|
fn (MalClient $jikan, ?int $page = null) => $jikan->getAnimeVideosEpisodes(new AnimeVideosEpisodesRequest($id, $page)),
|
||||||
|
$requestParams->get("page", 1)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
43
app/Features/AnimeVideosLookupHandler.php
Normal file
43
app/Features/AnimeVideosLookupHandler.php
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Features;
|
||||||
|
|
||||||
|
use App\Dto\AnimeVideosLookupCommand;
|
||||||
|
use App\Http\Resources\V4\AnimeVideosResource;
|
||||||
|
use App\Support\CachedData;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Jikan\MyAnimeList\MalClient;
|
||||||
|
use Jikan\Request\Anime\AnimeVideosRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends RequestHandlerWithScraperCache<AnimeVideosLookupCommand, JsonResponse>
|
||||||
|
*/
|
||||||
|
final class AnimeVideosLookupHandler extends RequestHandlerWithScraperCache
|
||||||
|
{
|
||||||
|
protected function resource(Collection $results): JsonResource
|
||||||
|
{
|
||||||
|
return new AnimeVideosResource(
|
||||||
|
$results->first()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function requestClass(): string
|
||||||
|
{
|
||||||
|
return AnimeVideosLookupCommand::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getScraperData(string $requestFingerPrint, Collection $requestParams): CachedData
|
||||||
|
{
|
||||||
|
$id = $requestParams->get("id");
|
||||||
|
|
||||||
|
return $this->scraperService->findList(
|
||||||
|
$requestFingerPrint,
|
||||||
|
fn (MalClient $jikan, ?int $page = null) => $jikan->getAnimeVideos(new AnimeVideosRequest($id))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -2,9 +2,8 @@
|
|||||||
|
|
||||||
namespace App\Features;
|
namespace App\Features;
|
||||||
|
|
||||||
use App\Concerns\ScraperResultCache;
|
use App\Contracts\CachedScraperService;
|
||||||
use App\Contracts\DataRequest;
|
use App\Dto\LookupDataCommand;
|
||||||
use App\Contracts\Repository;
|
|
||||||
use App\Contracts\RequestHandler;
|
use App\Contracts\RequestHandler;
|
||||||
use Illuminate\Http\Resources\Json\JsonResource;
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
use Illuminate\Http\Resources\Json\ResourceCollection;
|
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||||
@ -14,34 +13,28 @@ use Spatie\LaravelData\Data;
|
|||||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @template TRequest of DataRequest<TResponse>
|
* @template TRequest of LookupDataCommand<TResponse>
|
||||||
* @template TResponse of ResourceCollection|JsonResource|Response
|
* @template TResponse of ResourceCollection|JsonResource|Response
|
||||||
* @implements RequestHandler<TRequest, TResponse>
|
* @implements RequestHandler<TRequest, TResponse>
|
||||||
*/
|
*/
|
||||||
abstract class ItemLookupHandler extends Data implements RequestHandler
|
abstract class ItemLookupHandler extends Data implements RequestHandler
|
||||||
{
|
{
|
||||||
use ScraperResultCache;
|
public function __construct(protected readonly CachedScraperService $scraperService)
|
||||||
|
|
||||||
public function __construct(protected readonly Repository $repository)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param TRequest $request
|
* @param TRequest|LookupDataCommand<TResponse> $request
|
||||||
* @return TResponse
|
* @return TResponse
|
||||||
* @throws NotFoundHttpException
|
* @throws NotFoundHttpException
|
||||||
*/
|
*/
|
||||||
public function handle($request)
|
public function handle($request)
|
||||||
{
|
{
|
||||||
$requestFingerprint = $request->getFingerPrint();
|
$requestFingerprint = $request->getFingerPrint();
|
||||||
$results = $this->queryFromScraperCacheById(
|
$results = $this->scraperService->find($request->id, $requestFingerprint);
|
||||||
$this->repository,
|
|
||||||
$request->id,
|
|
||||||
$request->getFingerPrint(),
|
|
||||||
);
|
|
||||||
|
|
||||||
$resource = $this->resource($results);
|
$resource = $this->resource($results->collect());
|
||||||
return $this->prepareResponse($requestFingerprint, $results, $resource->response());
|
return $this->scraperService->augmentResponse($resource->response(), $requestFingerprint, $results);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract function resource(Collection $results): JsonResource;
|
protected abstract function resource(Collection $results): JsonResource;
|
||||||
|
28
app/Features/QueryAnimeHandler.php
Normal file
28
app/Features/QueryAnimeHandler.php
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Features;
|
||||||
|
|
||||||
|
use App\Dto\AnimeLookupCommand;
|
||||||
|
use App\Http\Resources\V4\AnimeResource;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends ItemLookupHandler<AnimeLookupCommand, JsonResponse>
|
||||||
|
*/
|
||||||
|
final class QueryAnimeHandler extends ItemLookupHandler
|
||||||
|
{
|
||||||
|
protected function resource(Collection $results): JsonResource
|
||||||
|
{
|
||||||
|
return new AnimeResource($results->first());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function requestClass(): string
|
||||||
|
{
|
||||||
|
return AnimeLookupCommand::class;
|
||||||
|
}
|
||||||
|
}
|
@ -2,9 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Features;
|
namespace App\Features;
|
||||||
|
|
||||||
use App\Concerns\ScraperResultCache;
|
use App\Dto\AnimeFullLookupCommand;
|
||||||
use App\Contracts\AnimeRepository;
|
|
||||||
use App\Dto\QueryFullAnimeCommand;
|
|
||||||
use App\Http\Resources\V4\AnimeFullResource;
|
use App\Http\Resources\V4\AnimeFullResource;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
use Illuminate\Http\Resources\Json\JsonResource;
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
@ -12,18 +10,13 @@ use Illuminate\Support\Collection;
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @extends ItemLookupHandler<QueryFullAnimeCommand, JsonResponse>
|
* @extends ItemLookupHandler<AnimeFullLookupCommand, JsonResponse>
|
||||||
*/
|
*/
|
||||||
final class QueryFullAnimeHandler extends ItemLookupHandler
|
final class QueryFullAnimeHandler extends ItemLookupHandler
|
||||||
{
|
{
|
||||||
public function __construct(AnimeRepository $repository)
|
|
||||||
{
|
|
||||||
parent::__construct($repository);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function requestClass(): string
|
public function requestClass(): string
|
||||||
{
|
{
|
||||||
return QueryFullAnimeCommand::class;
|
return AnimeFullLookupCommand::class;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function resource(Collection $results): JsonResource
|
protected function resource(Collection $results): JsonResource
|
||||||
|
@ -2,42 +2,19 @@
|
|||||||
|
|
||||||
namespace App\Features;
|
namespace App\Features;
|
||||||
|
|
||||||
use App\Concerns\ScraperResultCache;
|
|
||||||
use App\Contracts\RequestHandler;
|
|
||||||
use App\Dto\QueryTopReviewsCommand;
|
use App\Dto\QueryTopReviewsCommand;
|
||||||
use App\Http\Resources\V4\ResultsResource;
|
use App\Support\CachedData;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
use Jikan\Helper\Constants;
|
use Jikan\Helper\Constants;
|
||||||
use Jikan\MyAnimeList\MalClient;
|
use Jikan\MyAnimeList\MalClient;
|
||||||
use Jikan\Request\Reviews\RecentReviewsRequest;
|
use Jikan\Request\Reviews\RecentReviewsRequest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @implements RequestHandler<QueryTopReviewsCommand, JsonResponse>
|
* @extends RequestHandlerWithScraperCache<QueryTopReviewsCommand, JsonResponse>
|
||||||
*/
|
*/
|
||||||
class QueryTopReviewsHandler implements RequestHandler
|
final class QueryTopReviewsHandler extends RequestHandlerWithScraperCache
|
||||||
{
|
{
|
||||||
use ScraperResultCache;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param QueryTopReviewsCommand $request
|
|
||||||
* @returns JsonResponse
|
|
||||||
*/
|
|
||||||
public function handle($request): JsonResponse
|
|
||||||
{
|
|
||||||
$requestParams = collect($request->all());
|
|
||||||
$requestFingerPrint = $request->getFingerPrint();
|
|
||||||
$results = $this->queryFromScraperCacheByFingerPrint(
|
|
||||||
"reviews",
|
|
||||||
$requestFingerPrint,
|
|
||||||
fn (MalClient $jikan, int $page) => $jikan->getRecentReviews(
|
|
||||||
new RecentReviewsRequest(Constants::RECENT_REVIEW_BEST_VOTED, $page)
|
|
||||||
), $requestParams->get("page"));
|
|
||||||
|
|
||||||
return $this->prepareResponse($requestFingerPrint, $results, (new ResultsResource(
|
|
||||||
$results->first()
|
|
||||||
))->response());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
@ -45,4 +22,12 @@ class QueryTopReviewsHandler implements RequestHandler
|
|||||||
{
|
{
|
||||||
return QueryTopReviewsCommand::class;
|
return QueryTopReviewsCommand::class;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function getScraperData(string $requestFingerPrint, Collection $requestParams): CachedData
|
||||||
|
{
|
||||||
|
return $this->scraperService->findList(
|
||||||
|
$requestFingerPrint,
|
||||||
|
fn (MalClient $jikan, ?int $page = null) => $jikan->getRecentReviews(new RecentReviewsRequest(Constants::RECENT_REVIEW_BEST_VOTED, $page)),
|
||||||
|
$requestParams->get("page"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
58
app/Features/RequestHandlerWithScraperCache.php
Normal file
58
app/Features/RequestHandlerWithScraperCache.php
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Features;
|
||||||
|
|
||||||
|
use App\Contracts\CachedScraperService;
|
||||||
|
use App\Contracts\RequestHandler;
|
||||||
|
use App\Contracts\DataRequest;
|
||||||
|
use App\Http\Resources\V4\ResultsResource;
|
||||||
|
use App\Support\CachedData;
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||||
|
use Illuminate\Http\Response;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template TRequest of DataRequest<TResponse>
|
||||||
|
* @template TResponse of ResourceCollection|JsonResource|Response
|
||||||
|
* @implements RequestHandler<TRequest, TResponse>
|
||||||
|
*/
|
||||||
|
abstract class RequestHandlerWithScraperCache implements RequestHandler
|
||||||
|
{
|
||||||
|
public function __construct(protected readonly CachedScraperService $scraperService)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function handle($request)
|
||||||
|
{
|
||||||
|
$requestParams = collect($request->all());
|
||||||
|
$requestFingerPrint = $request->getFingerPrint();
|
||||||
|
$results = $this->getScraperData($requestFingerPrint, $requestParams);
|
||||||
|
|
||||||
|
return $this->renderResponse($requestFingerPrint, $results);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract function getScraperData(string $requestFingerPrint, Collection $requestParams): CachedData;
|
||||||
|
|
||||||
|
protected function resource(Collection $results): JsonResource
|
||||||
|
{
|
||||||
|
return new ResultsResource(
|
||||||
|
$results->first()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $requestFingerPrint
|
||||||
|
* @param CachedData $results
|
||||||
|
* @return TResponse
|
||||||
|
*/
|
||||||
|
protected function renderResponse(string $requestFingerPrint, CachedData $results)
|
||||||
|
{
|
||||||
|
$finalResults = $results->collect();
|
||||||
|
$response = $this->resource($finalResults)->response();
|
||||||
|
return $this->scraperService->augmentResponse($response, $requestFingerPrint, $results);
|
||||||
|
}
|
||||||
|
}
|
29
app/Features/UserByIdLookupHandler.php
Normal file
29
app/Features/UserByIdLookupHandler.php
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Features;
|
||||||
|
|
||||||
|
use App\Dto\UserByIdLookupCommand;
|
||||||
|
use App\Support\CachedData;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Jikan\MyAnimeList\MalClient;
|
||||||
|
use Jikan\Request\User\UsernameByIdRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends RequestHandlerWithScraperCache<UserByIdLookupCommand, JsonResponse>
|
||||||
|
*/
|
||||||
|
final class UserByIdLookupHandler extends RequestHandlerWithScraperCache
|
||||||
|
{
|
||||||
|
public function requestClass(): string
|
||||||
|
{
|
||||||
|
return UserByIdLookupCommand::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getScraperData(string $requestFingerPrint, Collection $requestParams): CachedData
|
||||||
|
{
|
||||||
|
return $this->scraperService->findList(
|
||||||
|
$requestFingerPrint,
|
||||||
|
fn (MalClient $jikan, ?int $page = null) => collect(['results' => $jikan->getUsernameById(new UsernameByIdRequest($requestParams->get("id")))])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -2,21 +2,18 @@
|
|||||||
|
|
||||||
namespace App\Features;
|
namespace App\Features;
|
||||||
|
|
||||||
use App\Concerns\ScraperResultCache;
|
|
||||||
use App\Contracts\RequestHandler;
|
|
||||||
use App\Dto\UsersSearchCommand;
|
use App\Dto\UsersSearchCommand;
|
||||||
use App\Http\Resources\V4\ResultsResource;
|
use App\Support\CachedData;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
use Jikan\MyAnimeList\MalClient;
|
use Jikan\MyAnimeList\MalClient;
|
||||||
use Jikan\Request\Search\UserSearchRequest;
|
use Jikan\Request\Search\UserSearchRequest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @implements RequestHandler<UsersSearchCommand, JsonResponse>
|
* @implements RequestHandlerWithScraperCache<UsersSearchCommand, JsonResponse>
|
||||||
*/
|
*/
|
||||||
final class UserSearchHandler implements RequestHandler
|
final class UserSearchHandler extends RequestHandlerWithScraperCache
|
||||||
{
|
{
|
||||||
use ScraperResultCache;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
@ -25,16 +22,9 @@ final class UserSearchHandler implements RequestHandler
|
|||||||
return UsersSearchCommand::class;
|
return UsersSearchCommand::class;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
protected function getScraperData(string $requestFingerPrint, Collection $requestParams): CachedData
|
||||||
* @param UsersSearchCommand $request
|
|
||||||
* @return JsonResponse
|
|
||||||
*/
|
|
||||||
public function handle($request): JsonResponse
|
|
||||||
{
|
{
|
||||||
$requestParams = collect($request->all());
|
return $this->scraperService->findList(
|
||||||
$requestFingerPrint = $request->getFingerPrint();
|
|
||||||
$results = $this->queryFromScraperCacheByFingerPrint(
|
|
||||||
"users",
|
|
||||||
$requestFingerPrint,
|
$requestFingerPrint,
|
||||||
fn (MalClient $jikan, int $page) => $jikan->getUserSearch((new UserSearchRequest())
|
fn (MalClient $jikan, int $page) => $jikan->getUserSearch((new UserSearchRequest())
|
||||||
->setQuery($requestParams->get("q"))
|
->setQuery($requestParams->get("q"))
|
||||||
@ -45,9 +35,5 @@ final class UserSearchHandler implements RequestHandler
|
|||||||
->setPage($page)),
|
->setPage($page)),
|
||||||
$requestParams->get("page")
|
$requestParams->get("page")
|
||||||
);
|
);
|
||||||
|
|
||||||
return $this->prepareResponse($requestFingerPrint, $results, (new ResultsResource(
|
|
||||||
$results->first()
|
|
||||||
))->response());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,45 +2,27 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers\V4DB;
|
namespace App\Http\Controllers\V4DB;
|
||||||
|
|
||||||
use App\Anime;
|
use App\Dto\AnimeCharactersLookupCommand;
|
||||||
use App\Http\HttpHelper;
|
use App\Dto\AnimeEpisodeLookupCommand;
|
||||||
use App\Http\HttpResponse;
|
use App\Dto\AnimeEpisodesLookupCommand;
|
||||||
use App\Http\Resources\V4\AnimeCharactersResource;
|
use App\Dto\AnimeExternalLookupCommand;
|
||||||
use App\Http\Resources\V4\AnimeEpisodeResource;
|
use App\Dto\AnimeForumLookupCommand;
|
||||||
use App\Http\Resources\V4\ExternalLinksResource;
|
use App\Dto\AnimeFullLookupCommand;
|
||||||
use App\Http\Resources\V4\AnimeRelationsResource;
|
use App\Dto\AnimeLookupCommand;
|
||||||
use App\Http\Resources\V4\AnimeThemesResource;
|
use App\Dto\AnimeMoreInfoLookupCommand;
|
||||||
use App\Http\Resources\V4\MoreInfoResource;
|
use App\Dto\AnimeNewsLookupCommand;
|
||||||
use App\Http\Resources\V4\PicturesResource;
|
use App\Dto\AnimePicturesLookupCommand;
|
||||||
use App\Http\Resources\V4\RecommendationsResource;
|
use App\Dto\AnimeRecommendationsLookupCommand;
|
||||||
use App\Http\Resources\V4\ResultsResource;
|
use App\Dto\AnimeRelationsLookupCommand;
|
||||||
use App\Http\Resources\V4\AnimeStaffResource;
|
use App\Dto\AnimeReviewsLookupCommand;
|
||||||
use App\Http\Resources\V4\AnimeStatisticsResource;
|
use App\Dto\AnimeStaffLookupCommand;
|
||||||
use App\Http\Resources\V4\StreamingLinksResource;
|
use App\Dto\AnimeStatsLookupCommand;
|
||||||
use App\Http\Resources\V4\UserUpdatesResource;
|
use App\Dto\AnimeStreamingLookupCommand;
|
||||||
use App\Http\Resources\V4\AnimeVideosResource;
|
use App\Dto\AnimeThemesLookupCommand;
|
||||||
use App\Http\Resources\V4\ForumResource;
|
use App\Dto\AnimeUserUpdatesLookupCommand;
|
||||||
|
use App\Dto\AnimeVideosEpisodesLookupCommand;
|
||||||
|
use App\Dto\AnimeVideosLookupCommand;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\DB;
|
|
||||||
use Jikan\Helper\Constants;
|
|
||||||
use Jikan\Request\Anime\AnimeCharactersAndStaffRequest;
|
|
||||||
use Jikan\Request\Anime\AnimeEpisodeRequest;
|
|
||||||
use Jikan\Request\Anime\AnimeEpisodesRequest;
|
|
||||||
use Jikan\Request\Anime\AnimeForumRequest;
|
|
||||||
use Jikan\Request\Anime\AnimeMoreInfoRequest;
|
|
||||||
use Jikan\Request\Anime\AnimeNewsRequest;
|
|
||||||
use Jikan\Request\Anime\AnimePicturesRequest;
|
|
||||||
use Jikan\Request\Anime\AnimeRecentlyUpdatedByUsersRequest;
|
|
||||||
use Jikan\Request\Anime\AnimeRecommendationsRequest;
|
|
||||||
use Jikan\Request\Anime\AnimeRequest;
|
|
||||||
use Jikan\Request\Anime\AnimeReviewsRequest;
|
|
||||||
use Jikan\Request\Anime\AnimeStatsRequest;
|
|
||||||
use Jikan\Request\Anime\AnimeVideosEpisodesRequest;
|
|
||||||
use Jikan\Request\Anime\AnimeVideosRequest;
|
|
||||||
use Laravel\Lumen\Http\ResponseFactory;
|
|
||||||
use MongoDB\BSON\UTCDateTime;
|
|
||||||
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
|
|
||||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
|
||||||
|
|
||||||
class AnimeController extends Controller
|
class AnimeController extends Controller
|
||||||
{
|
{
|
||||||
@ -75,59 +57,8 @@ class AnimeController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function full(Request $request, int $id)
|
public function full(Request $request, int $id)
|
||||||
{
|
{
|
||||||
$results = Anime::query()
|
$command = AnimeFullLookupCommand::from($request, $id);
|
||||||
->where('mal_id', $id)
|
return $this->mediator->send($command);
|
||||||
->get();
|
|
||||||
|
|
||||||
if (
|
|
||||||
$results->isEmpty()
|
|
||||||
|| $this->isExpired($request, $results)
|
|
||||||
) {
|
|
||||||
$response = Anime::scrape($id);
|
|
||||||
|
|
||||||
if (HttpHelper::hasError($response)) {
|
|
||||||
return HttpResponse::notFound($request);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($results->isEmpty()) {
|
|
||||||
$meta = [
|
|
||||||
'createdAt' => new UTCDateTime(),
|
|
||||||
'modifiedAt' => new UTCDateTime(),
|
|
||||||
'request_hash' => $this->fingerprint
|
|
||||||
];
|
|
||||||
}
|
|
||||||
$meta['modifiedAt'] = new UTCDateTime();
|
|
||||||
|
|
||||||
$response = $meta + $response;
|
|
||||||
|
|
||||||
if ($results->isEmpty()) {
|
|
||||||
Anime::create($response);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->isExpired($request, $results)) {
|
|
||||||
Anime::query()
|
|
||||||
->where('mal_id', $id)
|
|
||||||
->update($response);
|
|
||||||
}
|
|
||||||
|
|
||||||
$results = Anime::query()
|
|
||||||
->where('mal_id', $id)
|
|
||||||
->get();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($results->isEmpty()) {
|
|
||||||
return HttpResponse::notFound($request);
|
|
||||||
}
|
|
||||||
|
|
||||||
$response = (new \App\Http\Resources\V4\AnimeFullResource(
|
|
||||||
$results->first()
|
|
||||||
))->response();
|
|
||||||
|
|
||||||
return $this->prepareResponse(
|
|
||||||
$response,
|
|
||||||
$results,
|
|
||||||
$request
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -161,59 +92,8 @@ class AnimeController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function main(Request $request, int $id)
|
public function main(Request $request, int $id)
|
||||||
{
|
{
|
||||||
$results = Anime::query()
|
$command = AnimeLookupCommand::from($request, $id);
|
||||||
->where('mal_id', $id)
|
return $this->mediator->send($command);
|
||||||
->get();
|
|
||||||
|
|
||||||
if (
|
|
||||||
$results->isEmpty()
|
|
||||||
|| $this->isExpired($request, $results)
|
|
||||||
) {
|
|
||||||
$response = Anime::scrape($id);
|
|
||||||
|
|
||||||
if (HttpHelper::hasError($response)) {
|
|
||||||
return HttpResponse::notFound($request);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($results->isEmpty()) {
|
|
||||||
$meta = [
|
|
||||||
'createdAt' => new UTCDateTime(),
|
|
||||||
'modifiedAt' => new UTCDateTime(),
|
|
||||||
'request_hash' => $this->fingerprint
|
|
||||||
];
|
|
||||||
}
|
|
||||||
$meta['modifiedAt'] = new UTCDateTime();
|
|
||||||
|
|
||||||
$response = $meta + $response;
|
|
||||||
|
|
||||||
if ($results->isEmpty()) {
|
|
||||||
Anime::create($response);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->isExpired($request, $results)) {
|
|
||||||
Anime::query()
|
|
||||||
->where('mal_id', $id)
|
|
||||||
->update($response);
|
|
||||||
}
|
|
||||||
|
|
||||||
$results = Anime::query()
|
|
||||||
->where('mal_id', $id)
|
|
||||||
->get();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($results->isEmpty()) {
|
|
||||||
return HttpResponse::notFound($request);
|
|
||||||
}
|
|
||||||
|
|
||||||
$response = (new \App\Http\Resources\V4\AnimeResource(
|
|
||||||
$results->first()
|
|
||||||
))->response();
|
|
||||||
|
|
||||||
return $this->prepareResponse(
|
|
||||||
$response,
|
|
||||||
$results,
|
|
||||||
$request
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -244,29 +124,8 @@ class AnimeController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function characters(Request $request, int $id)
|
public function characters(Request $request, int $id)
|
||||||
{
|
{
|
||||||
$results = DB::table($this->getRouteTable($request))
|
$command = AnimeCharactersLookupCommand::from($request, $id);
|
||||||
->where('request_hash', $this->fingerprint)
|
return $this->mediator->send($command);
|
||||||
->get();
|
|
||||||
|
|
||||||
if (
|
|
||||||
$results->isEmpty()
|
|
||||||
|| $this->isExpired($request, $results)
|
|
||||||
) {
|
|
||||||
$anime = $this->jikan->getAnimeCharactersAndStaff(new AnimeCharactersAndStaffRequest($id));
|
|
||||||
$response = \json_decode($this->serializer->serialize($anime, 'json'), true);
|
|
||||||
|
|
||||||
$results = $this->updateCache($request, $results, $response);
|
|
||||||
}
|
|
||||||
|
|
||||||
$response = (new AnimeCharactersResource(
|
|
||||||
$results->first()
|
|
||||||
))->response();
|
|
||||||
|
|
||||||
return $this->prepareResponse(
|
|
||||||
$response,
|
|
||||||
$results,
|
|
||||||
$request
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -297,30 +156,8 @@ class AnimeController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function staff(Request $request, int $id)
|
public function staff(Request $request, int $id)
|
||||||
{
|
{
|
||||||
$results = DB::table($this->getRouteTable($request))
|
$command = AnimeStaffLookupCommand::from($request, $id);
|
||||||
->where('request_hash', $this->fingerprint)
|
return $this->mediator->send($command);
|
||||||
->get();
|
|
||||||
|
|
||||||
if (
|
|
||||||
$results->isEmpty()
|
|
||||||
|| $this->isExpired($request, $results)
|
|
||||||
) {
|
|
||||||
$page = $request->get('page') ?? 1;
|
|
||||||
$anime = $this->jikan->getAnimeCharactersAndStaff(new AnimeCharactersAndStaffRequest($id));
|
|
||||||
$response = \json_decode($this->serializer->serialize($anime, 'json'), true);
|
|
||||||
|
|
||||||
$results = $this->updateCache($request, $results, $response);
|
|
||||||
}
|
|
||||||
|
|
||||||
$response = (new AnimeStaffResource(
|
|
||||||
$results->first()
|
|
||||||
))->response();
|
|
||||||
|
|
||||||
return $this->prepareResponse(
|
|
||||||
$response,
|
|
||||||
$results,
|
|
||||||
$request
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -426,30 +263,8 @@ class AnimeController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function episodes(Request $request, int $id)
|
public function episodes(Request $request, int $id)
|
||||||
{
|
{
|
||||||
$results = DB::table($this->getRouteTable($request))
|
$command = AnimeEpisodesLookupCommand::from($request, $id);
|
||||||
->where('request_hash', $this->fingerprint)
|
return $this->mediator->send($command);
|
||||||
->get();
|
|
||||||
|
|
||||||
if (
|
|
||||||
$results->isEmpty()
|
|
||||||
|| $this->isExpired($request, $results)
|
|
||||||
) {
|
|
||||||
$page = $request->get('page') ?? 1;
|
|
||||||
$anime = $this->jikan->getAnimeEpisodes(new AnimeEpisodesRequest($id, $page));
|
|
||||||
$response = \json_decode($this->serializer->serialize($anime, 'json'), true);
|
|
||||||
|
|
||||||
$results = $this->updateCache($request, $results, $response);
|
|
||||||
}
|
|
||||||
|
|
||||||
$response = (new ResultsResource(
|
|
||||||
$results->first()
|
|
||||||
))->response();
|
|
||||||
|
|
||||||
return $this->prepareResponse(
|
|
||||||
$response,
|
|
||||||
$results,
|
|
||||||
$request
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -490,30 +305,8 @@ class AnimeController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function episode(Request $request, int $id, int $episodeId)
|
public function episode(Request $request, int $id, int $episodeId)
|
||||||
{
|
{
|
||||||
$results = DB::table($this->getRouteTable($request))
|
$command = AnimeEpisodeLookupCommand::from($request, $id, $episodeId);
|
||||||
->where('request_hash', $this->fingerprint)
|
return $this->mediator->send($command);
|
||||||
->get();
|
|
||||||
|
|
||||||
if (
|
|
||||||
$results->isEmpty()
|
|
||||||
|| $this->isExpired($request, $results)
|
|
||||||
) {
|
|
||||||
$page = $request->get('page') ?? 1;
|
|
||||||
$anime = $this->jikan->getAnimeEpisode(new AnimeEpisodeRequest($id, $episodeId));
|
|
||||||
$response = \json_decode($this->serializer->serialize($anime, 'json'), true);
|
|
||||||
|
|
||||||
$results = $this->updateCache($request, $results, $response);
|
|
||||||
}
|
|
||||||
|
|
||||||
$response = (new AnimeEpisodeResource(
|
|
||||||
$results->first()
|
|
||||||
))->response();
|
|
||||||
|
|
||||||
return $this->prepareResponse(
|
|
||||||
$response,
|
|
||||||
$results,
|
|
||||||
$request
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -558,30 +351,8 @@ class AnimeController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function news(Request $request, int $id)
|
public function news(Request $request, int $id)
|
||||||
{
|
{
|
||||||
$results = DB::table($this->getRouteTable($request))
|
$command = AnimeNewsLookupCommand::from($request, $id);
|
||||||
->where('request_hash', $this->fingerprint)
|
return $this->mediator->send($command);
|
||||||
->get();
|
|
||||||
|
|
||||||
if (
|
|
||||||
$results->isEmpty()
|
|
||||||
|| $this->isExpired($request, $results)
|
|
||||||
) {
|
|
||||||
$page = $request->get('page') ?? 1;
|
|
||||||
$anime = $this->jikan->getNewsList(new AnimeNewsRequest($id, $page));
|
|
||||||
$response = \json_decode($this->serializer->serialize($anime, 'json'), true);
|
|
||||||
|
|
||||||
$results = $this->updateCache($request, $results, $response);
|
|
||||||
}
|
|
||||||
|
|
||||||
$response = (new ResultsResource(
|
|
||||||
$results->first()
|
|
||||||
))->response();
|
|
||||||
|
|
||||||
return $this->prepareResponse(
|
|
||||||
$response,
|
|
||||||
$results,
|
|
||||||
$request
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -620,35 +391,8 @@ class AnimeController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function forum(Request $request, int $id)
|
public function forum(Request $request, int $id)
|
||||||
{
|
{
|
||||||
$results = DB::table($this->getRouteTable($request))
|
$command = AnimeForumLookupCommand::from($request, $id);
|
||||||
->where('request_hash', $this->fingerprint)
|
return $this->mediator->send($command);
|
||||||
->get();
|
|
||||||
|
|
||||||
if (
|
|
||||||
$results->isEmpty()
|
|
||||||
|| $this->isExpired($request, $results)
|
|
||||||
) {
|
|
||||||
$topic = $request->get('topic');
|
|
||||||
|
|
||||||
if ($request->get('filter') != null) {
|
|
||||||
$topic = $request->get('filter');
|
|
||||||
}
|
|
||||||
|
|
||||||
$anime = ['topics' => $this->jikan->getAnimeForum(new AnimeForumRequest($id, $topic))];
|
|
||||||
$response = \json_decode($this->serializer->serialize($anime, 'json'), true);
|
|
||||||
|
|
||||||
$results = $this->updateCache($request, $results, $response);
|
|
||||||
}
|
|
||||||
|
|
||||||
$response = (new ForumResource(
|
|
||||||
$results->first()
|
|
||||||
))->response();
|
|
||||||
|
|
||||||
return $this->prepareResponse(
|
|
||||||
$response,
|
|
||||||
$results,
|
|
||||||
$request
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -679,29 +423,8 @@ class AnimeController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function videos(Request $request, int $id)
|
public function videos(Request $request, int $id)
|
||||||
{
|
{
|
||||||
$results = DB::table($this->getRouteTable($request))
|
$command = AnimeVideosLookupCommand::from($request, $id);
|
||||||
->where('request_hash', $this->fingerprint)
|
return $this->mediator->send($command);
|
||||||
->get();
|
|
||||||
|
|
||||||
if (
|
|
||||||
$results->isEmpty()
|
|
||||||
|| $this->isExpired($request, $results)
|
|
||||||
) {
|
|
||||||
$anime = $this->jikan->getAnimeVideos(new AnimeVideosRequest($id));
|
|
||||||
$response = \json_decode($this->serializer->serialize($anime, 'json'), true);
|
|
||||||
|
|
||||||
$results = $this->updateCache($request, $results, $response);
|
|
||||||
}
|
|
||||||
|
|
||||||
$response = (new AnimeVideosResource(
|
|
||||||
$results->first()
|
|
||||||
))->response();
|
|
||||||
|
|
||||||
return $this->prepareResponse(
|
|
||||||
$response,
|
|
||||||
$results,
|
|
||||||
$request
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -777,30 +500,8 @@ class AnimeController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function videosEpisodes(Request $request, int $id)
|
public function videosEpisodes(Request $request, int $id)
|
||||||
{
|
{
|
||||||
$results = DB::table($this->getRouteTable($request))
|
$command = AnimeVideosEpisodesLookupCommand::from($request, $id);
|
||||||
->where('request_hash', $this->fingerprint)
|
return $this->mediator->send($command);
|
||||||
->get();
|
|
||||||
|
|
||||||
if (
|
|
||||||
$results->isEmpty()
|
|
||||||
|| $this->isExpired($request, $results)
|
|
||||||
) {
|
|
||||||
$page = $request->get('page') ?? 1;
|
|
||||||
$anime = $this->jikan->getAnimeVideosEpisodes(new AnimeVideosEpisodesRequest($id, $page));
|
|
||||||
$response = \json_decode($this->serializer->serialize($anime, 'json'), true);
|
|
||||||
|
|
||||||
$results = $this->updateCache($request, $results, $response);
|
|
||||||
}
|
|
||||||
|
|
||||||
$response = (new AnimeEpisodesResource(
|
|
||||||
$results->first()
|
|
||||||
))->response();
|
|
||||||
|
|
||||||
return $this->prepareResponse(
|
|
||||||
$response,
|
|
||||||
$results,
|
|
||||||
$request
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -833,29 +534,8 @@ class AnimeController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function pictures(Request $request, int $id)
|
public function pictures(Request $request, int $id)
|
||||||
{
|
{
|
||||||
$results = DB::table($this->getRouteTable($request))
|
$command = AnimePicturesLookupCommand::from($request, $id);
|
||||||
->where('request_hash', $this->fingerprint)
|
return $this->mediator->send($command);
|
||||||
->get();
|
|
||||||
|
|
||||||
if (
|
|
||||||
$results->isEmpty()
|
|
||||||
|| $this->isExpired($request, $results)
|
|
||||||
) {
|
|
||||||
$anime = ['pictures' => $this->jikan->getAnimePictures(new AnimePicturesRequest($id))];
|
|
||||||
$response = \json_decode($this->serializer->serialize($anime, 'json'), true);
|
|
||||||
|
|
||||||
$results = $this->updateCache($request, $results, $response);
|
|
||||||
}
|
|
||||||
|
|
||||||
$response = (new PicturesResource(
|
|
||||||
$results->first()
|
|
||||||
))->response();
|
|
||||||
|
|
||||||
return $this->prepareResponse(
|
|
||||||
$response,
|
|
||||||
$results,
|
|
||||||
$request
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -886,29 +566,8 @@ class AnimeController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function stats(Request $request, int $id)
|
public function stats(Request $request, int $id)
|
||||||
{
|
{
|
||||||
$results = DB::table($this->getRouteTable($request))
|
$command = AnimeStatsLookupCommand::from($request, $id);
|
||||||
->where('request_hash', $this->fingerprint)
|
return $this->mediator->send($command);
|
||||||
->get();
|
|
||||||
|
|
||||||
if (
|
|
||||||
$results->isEmpty()
|
|
||||||
|| $this->isExpired($request, $results)
|
|
||||||
) {
|
|
||||||
$anime = $this->jikan->getAnimeStats(new AnimeStatsRequest($id));
|
|
||||||
$response = \json_decode($this->serializer->serialize($anime, 'json'), true);
|
|
||||||
|
|
||||||
$results = $this->updateCache($request, $results, $response);
|
|
||||||
}
|
|
||||||
|
|
||||||
$response = (new AnimeStatisticsResource(
|
|
||||||
$results->first()
|
|
||||||
))->response();
|
|
||||||
|
|
||||||
return $this->prepareResponse(
|
|
||||||
$response,
|
|
||||||
$results,
|
|
||||||
$request
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -939,29 +598,8 @@ class AnimeController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function moreInfo(Request $request, int $id)
|
public function moreInfo(Request $request, int $id)
|
||||||
{
|
{
|
||||||
$results = DB::table($this->getRouteTable($request))
|
$command = AnimeMoreInfoLookupCommand::from($request, $id);
|
||||||
->where('request_hash', $this->fingerprint)
|
return $this->mediator->send($command);
|
||||||
->get();
|
|
||||||
|
|
||||||
if (
|
|
||||||
$results->isEmpty()
|
|
||||||
|| $this->isExpired($request, $results)
|
|
||||||
) {
|
|
||||||
$anime = ['moreinfo' => $this->jikan->getAnimeMoreInfo(new AnimeMoreInfoRequest($id))];
|
|
||||||
$response = \json_decode($this->serializer->serialize($anime, 'json'), true);
|
|
||||||
|
|
||||||
$results = $this->updateCache($request, $results, $response);
|
|
||||||
}
|
|
||||||
|
|
||||||
$response = (new MoreInfoResource(
|
|
||||||
$results->first()
|
|
||||||
))->response();
|
|
||||||
|
|
||||||
return $this->prepareResponse(
|
|
||||||
$response,
|
|
||||||
$results,
|
|
||||||
$request
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -992,29 +630,8 @@ class AnimeController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function recommendations(Request $request, int $id)
|
public function recommendations(Request $request, int $id)
|
||||||
{
|
{
|
||||||
$results = DB::table($this->getRouteTable($request))
|
$command = AnimeRecommendationsLookupCommand::from($request, $id);
|
||||||
->where('request_hash', $this->fingerprint)
|
return $this->mediator->send($command);
|
||||||
->get();
|
|
||||||
|
|
||||||
if (
|
|
||||||
$results->isEmpty()
|
|
||||||
|| $this->isExpired($request, $results)
|
|
||||||
) {
|
|
||||||
$anime = ['recommendations' => $this->jikan->getAnimeRecommendations(new AnimeRecommendationsRequest($id))];
|
|
||||||
$response = \json_decode($this->serializer->serialize($anime, 'json'), true);
|
|
||||||
|
|
||||||
$results = $this->updateCache($request, $results, $response);
|
|
||||||
}
|
|
||||||
|
|
||||||
$response = (new RecommendationsResource(
|
|
||||||
$results->first()
|
|
||||||
))->response();
|
|
||||||
|
|
||||||
return $this->prepareResponse(
|
|
||||||
$response,
|
|
||||||
$results,
|
|
||||||
$request
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1047,30 +664,8 @@ class AnimeController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function userupdates(Request $request, int $id)
|
public function userupdates(Request $request, int $id)
|
||||||
{
|
{
|
||||||
$results = DB::table($this->getRouteTable($request))
|
$command = AnimeUserUpdatesLookupCommand::from($request, $id);
|
||||||
->where('request_hash', $this->fingerprint)
|
return $this->mediator->send($command);
|
||||||
->get();
|
|
||||||
|
|
||||||
if (
|
|
||||||
$results->isEmpty()
|
|
||||||
|| $this->isExpired($request, $results)
|
|
||||||
) {
|
|
||||||
$page = $request->get('page') ?? 1;
|
|
||||||
$anime = $this->jikan->getAnimeRecentlyUpdatedByUsers(new AnimeRecentlyUpdatedByUsersRequest($id, $page));
|
|
||||||
$response = \json_decode($this->serializer->serialize($anime, 'json'), true);
|
|
||||||
|
|
||||||
$results = $this->updateCache($request, $results, $response);
|
|
||||||
}
|
|
||||||
|
|
||||||
$response = (new ResultsResource(
|
|
||||||
$results->first()
|
|
||||||
))->response();
|
|
||||||
|
|
||||||
return $this->prepareResponse(
|
|
||||||
$response,
|
|
||||||
$results,
|
|
||||||
$request
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1103,48 +698,8 @@ class AnimeController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function reviews(Request $request, int $id)
|
public function reviews(Request $request, int $id)
|
||||||
{
|
{
|
||||||
$results = DB::table($this->getRouteTable($request))
|
$command = AnimeReviewsLookupCommand::from($request, $id);
|
||||||
->where('request_hash', $this->fingerprint)
|
return $this->mediator->send($command);
|
||||||
->get();
|
|
||||||
|
|
||||||
if (
|
|
||||||
$results->isEmpty()
|
|
||||||
|| $this->isExpired($request, $results)
|
|
||||||
) {
|
|
||||||
$page = $request->get('page') ?? 1;
|
|
||||||
$sort = $request->get('sort') ?? Constants::REVIEWS_SORT_MOST_VOTED;
|
|
||||||
|
|
||||||
if (!in_array($sort, [Constants::REVIEWS_SORT_MOST_VOTED, Constants::REVIEWS_SORT_NEWEST, Constants::REVIEWS_SORT_OLDEST])) {
|
|
||||||
throw new BadRequestException('Invalid sort for reviews. Please refer to the documentation: https://docs.api.jikan.moe/');
|
|
||||||
}
|
|
||||||
|
|
||||||
$spoilers = $request->get('spoilers') ?? false;
|
|
||||||
$preliminary = $request->get('preliminary') ?? false;
|
|
||||||
|
|
||||||
$anime = $this->jikan
|
|
||||||
->getAnimeReviews(
|
|
||||||
new AnimeReviewsRequest(
|
|
||||||
$id,
|
|
||||||
$page,
|
|
||||||
$sort,
|
|
||||||
$spoilers,
|
|
||||||
$preliminary
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
$response = \json_decode($this->serializer->serialize($anime, 'json'), true);
|
|
||||||
$results = $this->updateCache($request, $results, $response);
|
|
||||||
}
|
|
||||||
|
|
||||||
$response = (new ResultsResource(
|
|
||||||
$results->first()
|
|
||||||
))->response();
|
|
||||||
|
|
||||||
return $this->prepareResponse(
|
|
||||||
$response,
|
|
||||||
$results,
|
|
||||||
$request
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -1184,55 +739,8 @@ class AnimeController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function relations(Request $request, int $id)
|
public function relations(Request $request, int $id)
|
||||||
{
|
{
|
||||||
$results = Anime::query()
|
$command = AnimeRelationsLookupCommand::from($request, $id);
|
||||||
->where('mal_id', $id)
|
return $this->mediator->send($command);
|
||||||
->get();
|
|
||||||
|
|
||||||
if (
|
|
||||||
$results->isEmpty()
|
|
||||||
|| $this->isExpired($request, $results)
|
|
||||||
) {
|
|
||||||
$response = Anime::scrape($id);
|
|
||||||
|
|
||||||
if ($results->isEmpty()) {
|
|
||||||
$meta = [
|
|
||||||
'createdAt' => new UTCDateTime(),
|
|
||||||
'modifiedAt' => new UTCDateTime(),
|
|
||||||
'request_hash' => $this->fingerprint
|
|
||||||
];
|
|
||||||
}
|
|
||||||
$meta['modifiedAt'] = new UTCDateTime();
|
|
||||||
|
|
||||||
$response = $meta + $response;
|
|
||||||
|
|
||||||
if ($results->isEmpty()) {
|
|
||||||
Anime::create($response);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->isExpired($request, $results)) {
|
|
||||||
Anime::query()
|
|
||||||
->where('mal_id', $id)
|
|
||||||
->update($response);
|
|
||||||
}
|
|
||||||
|
|
||||||
$results = Anime::query()
|
|
||||||
->where('mal_id', $id)
|
|
||||||
->get();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($results->isEmpty()) {
|
|
||||||
return HttpResponse::notFound($request);
|
|
||||||
}
|
|
||||||
|
|
||||||
$response = (new AnimeRelationsResource(
|
|
||||||
$results->first()
|
|
||||||
))->response();
|
|
||||||
|
|
||||||
return $this->prepareResponse(
|
|
||||||
$response,
|
|
||||||
$results,
|
|
||||||
$request
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1263,56 +771,8 @@ class AnimeController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function themes(Request $request, int $id)
|
public function themes(Request $request, int $id)
|
||||||
{
|
{
|
||||||
$results = Anime::query()
|
$command = AnimeThemesLookupCommand::from($request, $id);
|
||||||
->where('mal_id', $id)
|
return $this->mediator->send($command);
|
||||||
->get();
|
|
||||||
|
|
||||||
if (
|
|
||||||
$results->isEmpty()
|
|
||||||
|| $this->isExpired($request, $results)
|
|
||||||
) {
|
|
||||||
$response = Anime::scrape($id);
|
|
||||||
|
|
||||||
if ($results->isEmpty()) {
|
|
||||||
$meta = [
|
|
||||||
'createdAt' => new UTCDateTime(),
|
|
||||||
'modifiedAt' => new UTCDateTime(),
|
|
||||||
'request_hash' => $this->fingerprint
|
|
||||||
];
|
|
||||||
}
|
|
||||||
$meta['modifiedAt'] = new UTCDateTime();
|
|
||||||
|
|
||||||
$response = $meta + $response;
|
|
||||||
|
|
||||||
if ($results->isEmpty()) {
|
|
||||||
Anime::create($response);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->isExpired($request, $results)) {
|
|
||||||
Anime::query()
|
|
||||||
->where('mal_id', $id)
|
|
||||||
->update($response);
|
|
||||||
}
|
|
||||||
|
|
||||||
$results = Anime::query()
|
|
||||||
->where('mal_id', $id)
|
|
||||||
->get();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($results->isEmpty()) {
|
|
||||||
return HttpResponse::notFound($request);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
$response = (new AnimeThemesResource(
|
|
||||||
$results->first()
|
|
||||||
))->response();
|
|
||||||
|
|
||||||
return $this->prepareResponse(
|
|
||||||
$response,
|
|
||||||
$results,
|
|
||||||
$request
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1343,56 +803,8 @@ class AnimeController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function external(Request $request, int $id)
|
public function external(Request $request, int $id)
|
||||||
{
|
{
|
||||||
$results = Anime::query()
|
$command = AnimeExternalLookupCommand::from($request, $id);
|
||||||
->where('mal_id', $id)
|
return $this->mediator->send($command);
|
||||||
->get();
|
|
||||||
|
|
||||||
if (
|
|
||||||
$results->isEmpty()
|
|
||||||
|| $this->isExpired($request, $results)
|
|
||||||
) {
|
|
||||||
$response = Anime::scrape($id);
|
|
||||||
|
|
||||||
if ($results->isEmpty()) {
|
|
||||||
$meta = [
|
|
||||||
'createdAt' => new UTCDateTime(),
|
|
||||||
'modifiedAt' => new UTCDateTime(),
|
|
||||||
'request_hash' => $this->fingerprint
|
|
||||||
];
|
|
||||||
}
|
|
||||||
$meta['modifiedAt'] = new UTCDateTime();
|
|
||||||
|
|
||||||
$response = $meta + $response;
|
|
||||||
|
|
||||||
if ($results->isEmpty()) {
|
|
||||||
Anime::create($response);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->isExpired($request, $results)) {
|
|
||||||
Anime::query()
|
|
||||||
->where('mal_id', $id)
|
|
||||||
->update($response);
|
|
||||||
}
|
|
||||||
|
|
||||||
$results = Anime::query()
|
|
||||||
->where('mal_id', $id)
|
|
||||||
->get();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($results->isEmpty()) {
|
|
||||||
return HttpResponse::notFound($request);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
$response = (new ExternalLinksResource(
|
|
||||||
$results->first()
|
|
||||||
))->response();
|
|
||||||
|
|
||||||
return $this->prepareResponse(
|
|
||||||
$response,
|
|
||||||
$results,
|
|
||||||
$request
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1423,56 +835,7 @@ class AnimeController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function streaming(Request $request, int $id)
|
public function streaming(Request $request, int $id)
|
||||||
{
|
{
|
||||||
$results = Anime::query()
|
$command = AnimeStreamingLookupCommand::from($request, $id);
|
||||||
->where('mal_id', $id)
|
return $this->mediator->send($command);
|
||||||
->get();
|
|
||||||
|
|
||||||
if (
|
|
||||||
$results->isEmpty()
|
|
||||||
|| $this->isExpired($request, $results)
|
|
||||||
) {
|
|
||||||
$response = Anime::scrape($id);
|
|
||||||
|
|
||||||
if ($results->isEmpty()) {
|
|
||||||
$meta = [
|
|
||||||
'createdAt' => new UTCDateTime(),
|
|
||||||
'modifiedAt' => new UTCDateTime(),
|
|
||||||
'request_hash' => $this->fingerprint
|
|
||||||
];
|
|
||||||
}
|
|
||||||
$meta['modifiedAt'] = new UTCDateTime();
|
|
||||||
|
|
||||||
$response = $meta + $response;
|
|
||||||
|
|
||||||
if ($results->isEmpty()) {
|
|
||||||
Anime::query()
|
|
||||||
->insert($response);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->isExpired($request, $results)) {
|
|
||||||
Anime::query()
|
|
||||||
->where('mal_id', $id)
|
|
||||||
->update($response);
|
|
||||||
}
|
|
||||||
|
|
||||||
$results = Anime::query()
|
|
||||||
->where('mal_id', $id)
|
|
||||||
->get();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($results->isEmpty()) {
|
|
||||||
return HttpResponse::notFound($request);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
$response = (new StreamingLinksResource(
|
|
||||||
$results->first()
|
|
||||||
))->response();
|
|
||||||
|
|
||||||
return $this->prepareResponse(
|
|
||||||
$response,
|
|
||||||
$results,
|
|
||||||
$request
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ use App\Dto\ClubSearchCommand;
|
|||||||
use App\Dto\MangaSearchCommand;
|
use App\Dto\MangaSearchCommand;
|
||||||
use App\Dto\PeopleSearchCommand;
|
use App\Dto\PeopleSearchCommand;
|
||||||
use App\Dto\ProducersSearchCommand;
|
use App\Dto\ProducersSearchCommand;
|
||||||
|
use App\Dto\UserByIdLookupCommand;
|
||||||
use App\Dto\UsersSearchCommand;
|
use App\Dto\UsersSearchCommand;
|
||||||
use App\Http\Resources\V4\ResultsResource;
|
use App\Http\Resources\V4\ResultsResource;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
@ -518,29 +519,7 @@ class SearchController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function userById(Request $request, int $id)
|
public function userById(Request $request, int $id)
|
||||||
{
|
{
|
||||||
$results = DB::table($this->getRouteTable($request))
|
return $this->mediator->send(UserByIdLookupCommand::from($request, $id));
|
||||||
->where('request_hash', $this->fingerprint)
|
|
||||||
->get();
|
|
||||||
|
|
||||||
if (
|
|
||||||
$results->isEmpty()
|
|
||||||
|| $this->isExpired($request, $results)
|
|
||||||
) {
|
|
||||||
$anime = ['results'=>$this->jikan->getUsernameById(new UsernameByIdRequest($id))];
|
|
||||||
$response = \json_decode($this->serializer->serialize($anime, 'json'), true);
|
|
||||||
|
|
||||||
$results = $this->updateCache($request, $results, $response);
|
|
||||||
}
|
|
||||||
|
|
||||||
$response = (new ResultsResource(
|
|
||||||
$results->first()
|
|
||||||
))->response();
|
|
||||||
|
|
||||||
return $this->prepareResponse(
|
|
||||||
$response,
|
|
||||||
$results,
|
|
||||||
$request
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
namespace App\Providers;
|
namespace App\Providers;
|
||||||
|
|
||||||
use App\Contracts\AnimeRepository;
|
use App\Contracts\AnimeRepository;
|
||||||
|
use App\Contracts\CachedScraperService;
|
||||||
use App\Contracts\CharacterRepository;
|
use App\Contracts\CharacterRepository;
|
||||||
use App\Contracts\ClubRepository;
|
use App\Contracts\ClubRepository;
|
||||||
use App\Contracts\MagazineRepository;
|
use App\Contracts\MagazineRepository;
|
||||||
@ -15,18 +16,39 @@ use App\Contracts\RequestHandler;
|
|||||||
use App\Contracts\UnitOfWork;
|
use App\Contracts\UnitOfWork;
|
||||||
use App\Contracts\UserRepository;
|
use App\Contracts\UserRepository;
|
||||||
use App\Dto\QueryTopPeopleCommand;
|
use App\Dto\QueryTopPeopleCommand;
|
||||||
|
use App\Features\AnimeEpisodeLookupHandler;
|
||||||
|
use App\Features\AnimeEpisodesLookupHandler;
|
||||||
|
use App\Features\AnimeExternalLookupHandler;
|
||||||
|
use App\Features\AnimeForumLookupHandler;
|
||||||
use App\Features\AnimeGenreListHandler;
|
use App\Features\AnimeGenreListHandler;
|
||||||
|
use App\Features\AnimeMoreInfoLookupHandler;
|
||||||
|
use App\Features\AnimeNewsLookupHandler;
|
||||||
|
use App\Features\AnimePicturesLookupHandler;
|
||||||
|
use App\Features\AnimeRecommendationsLookupHandler;
|
||||||
|
use App\Features\AnimeRelationsLookupHandler;
|
||||||
|
use App\Features\AnimeReviewsLookupHandler;
|
||||||
use App\Features\AnimeSearchHandler;
|
use App\Features\AnimeSearchHandler;
|
||||||
|
use App\Features\AnimeCharactersLookupHandler;
|
||||||
|
use App\Features\AnimeStaffLookupHandler;
|
||||||
|
use App\Features\AnimeStatsLookupHandler;
|
||||||
|
use App\Features\AnimeStreamingLookupHandler;
|
||||||
|
use App\Features\AnimeThemesLookupHandler;
|
||||||
|
use App\Features\AnimeVideosEpisodesLookupHandler;
|
||||||
|
use App\Features\AnimeVideosLookupHandler;
|
||||||
use App\Features\CharacterSearchHandler;
|
use App\Features\CharacterSearchHandler;
|
||||||
use App\Features\ClubSearchHandler;
|
use App\Features\ClubSearchHandler;
|
||||||
use App\Features\MagazineSearchHandler;
|
use App\Features\MagazineSearchHandler;
|
||||||
|
use App\Features\MangaGenreListHandler;
|
||||||
use App\Features\MangaSearchHandler;
|
use App\Features\MangaSearchHandler;
|
||||||
use App\Features\PeopleSearchHandler;
|
use App\Features\PeopleSearchHandler;
|
||||||
use App\Features\ProducerSearchHandler;
|
use App\Features\ProducerSearchHandler;
|
||||||
|
use App\Features\QueryAnimeHandler;
|
||||||
|
use App\Features\QueryFullAnimeHandler;
|
||||||
use App\Features\QueryTopAnimeItemsHandler;
|
use App\Features\QueryTopAnimeItemsHandler;
|
||||||
use App\Features\QueryTopCharactersHandler;
|
use App\Features\QueryTopCharactersHandler;
|
||||||
use App\Features\QueryTopMangaItemsHandler;
|
use App\Features\QueryTopMangaItemsHandler;
|
||||||
use App\Features\QueryTopReviewsHandler;
|
use App\Features\QueryTopReviewsHandler;
|
||||||
|
use App\Features\UserByIdLookupHandler;
|
||||||
use App\Features\UserSearchHandler;
|
use App\Features\UserSearchHandler;
|
||||||
use App\GenreAnime;
|
use App\GenreAnime;
|
||||||
use App\GenreManga;
|
use App\GenreManga;
|
||||||
@ -52,6 +74,7 @@ use App\Repositories\DefaultPeopleRepository;
|
|||||||
use App\Repositories\DefaultProducerRepository;
|
use App\Repositories\DefaultProducerRepository;
|
||||||
use App\Repositories\DefaultUserRepository;
|
use App\Repositories\DefaultUserRepository;
|
||||||
use App\Repositories\MangaGenresRepository;
|
use App\Repositories\MangaGenresRepository;
|
||||||
|
use App\Services\DefaultCachedScraperService;
|
||||||
use App\Services\DefaultQueryBuilderService;
|
use App\Services\DefaultQueryBuilderService;
|
||||||
use App\Services\DefaultScoutSearchService;
|
use App\Services\DefaultScoutSearchService;
|
||||||
use App\Services\ElasticScoutSearchService;
|
use App\Services\ElasticScoutSearchService;
|
||||||
@ -67,6 +90,7 @@ use Illuminate\Contracts\Container\BindingResolutionException;
|
|||||||
use Illuminate\Contracts\Foundation\Application;
|
use Illuminate\Contracts\Foundation\Application;
|
||||||
use Illuminate\Support\ServiceProvider;
|
use Illuminate\Support\ServiceProvider;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
|
use Jikan\MyAnimeList\MalClient;
|
||||||
use Laravel\Scout\Builder as ScoutBuilder;
|
use Laravel\Scout\Builder as ScoutBuilder;
|
||||||
use Typesense\LaravelTypesense\Typesense;
|
use Typesense\LaravelTypesense\Typesense;
|
||||||
|
|
||||||
@ -195,6 +219,7 @@ class AppServiceProvider extends ServiceProvider
|
|||||||
$this->app->singleton(MangaGenresRepository::class);
|
$this->app->singleton(MangaGenresRepository::class);
|
||||||
|
|
||||||
$this->app->singleton(UnitOfWork::class, JikanUnitOfWork::class);
|
$this->app->singleton(UnitOfWork::class, JikanUnitOfWork::class);
|
||||||
|
$this->app->singleton(CachedScraperService::class, DefaultCachedScraperService::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function registerRequestHandlers()
|
private function registerRequestHandlers()
|
||||||
@ -209,9 +234,7 @@ class AppServiceProvider extends ServiceProvider
|
|||||||
* Validation for requests is specified in the DTOs.
|
* Validation for requests is specified in the DTOs.
|
||||||
* Querying/Filtering entirely happens on the model side.
|
* Querying/Filtering entirely happens on the model side.
|
||||||
* The lines below explicitly define the mapping between request handlers and repositories.
|
* The lines below explicitly define the mapping between request handlers and repositories.
|
||||||
* Repositories are just a bit of abstraction over models. (A step away from magic class strings)
|
* Repositories are just a bit of abstraction over models.
|
||||||
* Every request handler gets a dedicated instance of QueryBuilderService which will be configured with a
|
|
||||||
* specific repository instance.
|
|
||||||
*/
|
*/
|
||||||
$this->app->when(DefaultMediator::class)
|
$this->app->when(DefaultMediator::class)
|
||||||
->needs(RequestHandler::class)
|
->needs(RequestHandler::class)
|
||||||
@ -229,11 +252,10 @@ class AppServiceProvider extends ServiceProvider
|
|||||||
ClubSearchHandler::class => $unitOfWorkInstance->clubs(),
|
ClubSearchHandler::class => $unitOfWorkInstance->clubs(),
|
||||||
MagazineSearchHandler::class => $unitOfWorkInstance->magazines(),
|
MagazineSearchHandler::class => $unitOfWorkInstance->magazines(),
|
||||||
ProducerSearchHandler::class => $unitOfWorkInstance->producers(),
|
ProducerSearchHandler::class => $unitOfWorkInstance->producers(),
|
||||||
UserSearchHandler::class => $unitOfWorkInstance->users()
|
|
||||||
];
|
];
|
||||||
$requestHandlers = [];
|
$requestHandlers = [];
|
||||||
foreach ($searchRequestHandlersDescriptors as $handlerClass => $repositoryInstance) {
|
foreach ($searchRequestHandlersDescriptors as $handlerClass => $repositoryInstance) {
|
||||||
$requestHandlers[] = $this->app->make($handlerClass, [
|
$requestHandlers[] = $app->make($handlerClass, [
|
||||||
$app->make(DefaultQueryBuilderService::class, [
|
$app->make(DefaultQueryBuilderService::class, [
|
||||||
static::makeSearchService($app, $searchIndexesEnabled, $repositoryInstance),
|
static::makeSearchService($app, $searchIndexesEnabled, $repositoryInstance),
|
||||||
$app->make($searchIndexesEnabled ? ScoutBuilderPaginatorService::class : EloquentBuilderPaginatorService::class)
|
$app->make($searchIndexesEnabled ? ScoutBuilderPaginatorService::class : EloquentBuilderPaginatorService::class)
|
||||||
@ -249,23 +271,64 @@ class AppServiceProvider extends ServiceProvider
|
|||||||
];
|
];
|
||||||
|
|
||||||
foreach ($queryTopItemsDescriptors as $handlerClass => $repositoryInstance) {
|
foreach ($queryTopItemsDescriptors as $handlerClass => $repositoryInstance) {
|
||||||
$requestHandlers[] = $this->app->make($handlerClass, [
|
$requestHandlers[] = $app->make($handlerClass, [
|
||||||
$repositoryInstance,
|
$repositoryInstance,
|
||||||
// top queries don't use the search engine, so it's enough for them to use eloquent paginator
|
// top queries don't use the search engine, so it's enough for them to use eloquent paginator
|
||||||
$this->app->make(EloquentBuilderPaginatorService::class)
|
$app->make(EloquentBuilderPaginatorService::class)
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$genreRequestHandlerDescriptors = [
|
// request handlers which only depend on a repository instance
|
||||||
|
$requestHandlersWithOnlyRepositoryDependency = [
|
||||||
AnimeGenreListHandler::class => $unitOfWorkInstance->animeGenres(),
|
AnimeGenreListHandler::class => $unitOfWorkInstance->animeGenres(),
|
||||||
MangaGenresRepository::class => $unitOfWorkInstance->mangaGenres()
|
MangaGenreListHandler::class => $unitOfWorkInstance->mangaGenres(),
|
||||||
];
|
];
|
||||||
|
|
||||||
foreach ($genreRequestHandlerDescriptors as $handlerClass => $repositoryInstance) {
|
foreach ($requestHandlersWithOnlyRepositoryDependency as $handlerClass => $repositoryInstance) {
|
||||||
$requestHandlers[] = $this->app->make($handlerClass, [$repositoryInstance]);
|
$requestHandlers[] = $app->make($handlerClass, [$repositoryInstance]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$requestHandlers[] = $this->app->make(QueryTopReviewsHandler::class);
|
// request handlers which are fetching data through the jikan library from MAL, and caching the result.
|
||||||
|
$requestHandlersWithScraperService = [
|
||||||
|
QueryFullAnimeHandler::class => $unitOfWorkInstance->anime(),
|
||||||
|
QueryAnimeHandler::class => $unitOfWorkInstance->anime(),
|
||||||
|
UserSearchHandler::class => $unitOfWorkInstance->documents("common"),
|
||||||
|
QueryTopReviewsHandler::class => $unitOfWorkInstance->documents("common"),
|
||||||
|
UserByIdLookupHandler::class => $unitOfWorkInstance->documents("common"),
|
||||||
|
AnimeCharactersLookupHandler::class => $unitOfWorkInstance->documents("anime_characters_staff"),
|
||||||
|
AnimeStaffLookupHandler::class => $unitOfWorkInstance->documents("anime_characters_staff"),
|
||||||
|
AnimeEpisodesLookupHandler::class => $unitOfWorkInstance->documents("anime_episodes"),
|
||||||
|
AnimeEpisodeLookupHandler::class => $unitOfWorkInstance->documents("anime_episode"),
|
||||||
|
AnimeNewsLookupHandler::class => $unitOfWorkInstance->documents("anime_news"),
|
||||||
|
AnimeForumLookupHandler::class => $unitOfWorkInstance->documents("anime_forum"),
|
||||||
|
AnimeVideosLookupHandler::class => $unitOfWorkInstance->documents("anime_videos"),
|
||||||
|
AnimeVideosEpisodesLookupHandler::class => $unitOfWorkInstance->documents("anime_videos_episodes"),
|
||||||
|
AnimePicturesLookupHandler::class => $unitOfWorkInstance->documents("anime_pictures"),
|
||||||
|
AnimeStatsLookupHandler::class => $unitOfWorkInstance->documents("anime_stats"),
|
||||||
|
AnimeMoreInfoLookupHandler::class => $unitOfWorkInstance->documents("anime_moreinfo"),
|
||||||
|
AnimeRecommendationsLookupHandler::class => $unitOfWorkInstance->documents("anime_recommendations"),
|
||||||
|
AnimeReviewsLookupHandler::class => $unitOfWorkInstance->documents("anime_reviews"),
|
||||||
|
AnimeRelationsLookupHandler::class => $unitOfWorkInstance->anime(),
|
||||||
|
AnimeExternalLookupHandler::class => $unitOfWorkInstance->anime(),
|
||||||
|
AnimeStreamingLookupHandler::class => $unitOfWorkInstance->anime(),
|
||||||
|
AnimeThemesLookupHandler::class => $unitOfWorkInstance->anime(),
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($requestHandlersWithScraperService as $handlerClass => $repositoryInstance) {
|
||||||
|
$jikan = $app->make(MalClient::class);
|
||||||
|
$serializer = $app->make("SerializerV4");
|
||||||
|
$requestHandlers[] = $app->make($handlerClass, [
|
||||||
|
$app->make(DefaultCachedScraperService::class,
|
||||||
|
[$repositoryInstance, $jikan, $serializer])
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$requestHandlersWithNoDependencies = [
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($requestHandlersWithNoDependencies as $handlerClass) {
|
||||||
|
$requestHandlers[] = $this->app->make($handlerClass);
|
||||||
|
}
|
||||||
|
|
||||||
return $requestHandlers;
|
return $requestHandlers;
|
||||||
});
|
});
|
||||||
|
@ -4,7 +4,7 @@ namespace App\Repositories;
|
|||||||
|
|
||||||
use App\Contracts\Repository;
|
use App\Contracts\Repository;
|
||||||
use App\Support\RepositoryQuery;
|
use App\Support\RepositoryQuery;
|
||||||
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
use Illuminate\Contracts\Database\Query\Builder;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
class DatabaseRepository extends RepositoryQuery implements Repository
|
class DatabaseRepository extends RepositoryQuery implements Repository
|
||||||
@ -30,7 +30,7 @@ class DatabaseRepository extends RepositoryQuery implements Repository
|
|||||||
->get();
|
->get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function queryByMalId(int $id): EloquentBuilder
|
public function queryByMalId(int $id): Builder
|
||||||
{
|
{
|
||||||
return $this->queryable(true)
|
return $this->queryable(true)
|
||||||
->where('mal_id', $id);
|
->where('mal_id', $id);
|
||||||
@ -43,7 +43,7 @@ class DatabaseRepository extends RepositoryQuery implements Repository
|
|||||||
|
|
||||||
// fixme: this should not be here.
|
// fixme: this should not be here.
|
||||||
// this is here because we have the "scrape" static method on models
|
// this is here because we have the "scrape" static method on models
|
||||||
public function scrape(int $id): array
|
public function scrape(int|string $id): array
|
||||||
{
|
{
|
||||||
$modelClass = get_class($this->queryable(true)->newModelInstance());
|
$modelClass = get_class($this->queryable(true)->newModelInstance());
|
||||||
|
|
||||||
|
34
app/Repositories/DocumentRepository.php
Normal file
34
app/Repositories/DocumentRepository.php
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Repositories;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a table in the document database which doesn't have a model representation in the code base.
|
||||||
|
*/
|
||||||
|
final class DocumentRepository extends DatabaseRepository
|
||||||
|
{
|
||||||
|
private string $tableName;
|
||||||
|
|
||||||
|
public function __construct(string $tableName)
|
||||||
|
{
|
||||||
|
parent::__construct(fn() => DB::table($tableName), fn($x, $y) => throw new \Exception("Not supported"));
|
||||||
|
$this->tableName = $tableName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function scrape(int|string $id): array
|
||||||
|
{
|
||||||
|
throw new \Exception("Not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function tableName(): string
|
||||||
|
{
|
||||||
|
return $this->tableName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createEntity()
|
||||||
|
{
|
||||||
|
throw new \Exception("Not supported");
|
||||||
|
}
|
||||||
|
}
|
198
app/Services/DefaultCachedScraperService.php
Normal file
198
app/Services/DefaultCachedScraperService.php
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services;
|
||||||
|
|
||||||
|
use App\Concerns\ScraperCacheTtl;
|
||||||
|
use App\Contracts\CachedScraperService;
|
||||||
|
use App\Contracts\Repository;
|
||||||
|
use App\Http\HttpHelper;
|
||||||
|
use App\Support\CachedData;
|
||||||
|
use Illuminate\Contracts\Database\Query\Builder;
|
||||||
|
use Illuminate\Http\Response;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Jikan\MyAnimeList\MalClient;
|
||||||
|
use MongoDB\BSON\UTCDateTime;
|
||||||
|
use JMS\Serializer\Serializer;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A service which scrapes data from MAL if cache is expired or empty
|
||||||
|
*/
|
||||||
|
final class DefaultCachedScraperService implements CachedScraperService
|
||||||
|
{
|
||||||
|
use ScraperCacheTtl;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private readonly Repository $repository,
|
||||||
|
private readonly MalClient $jikan,
|
||||||
|
private readonly Serializer $serializer,
|
||||||
|
)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds cached scraper results by cacheKey, if not found scrapes them from MAL via the provided callback.
|
||||||
|
* @param string $cacheKey
|
||||||
|
* @param \Closure $getMalDataCallback
|
||||||
|
* @param int|null $page
|
||||||
|
* @return CachedData
|
||||||
|
*/
|
||||||
|
public function findList(string $cacheKey, \Closure $getMalDataCallback, ?int $page = null): CachedData
|
||||||
|
{
|
||||||
|
$results = $this->get($cacheKey);
|
||||||
|
|
||||||
|
if ($results->isEmpty() || $results->isExpired()) {
|
||||||
|
$page = $page ?? 1;
|
||||||
|
$data = $getMalDataCallback($this->jikan, $page);
|
||||||
|
$scraperResponse = $this->serializeScraperResult($data);
|
||||||
|
$results = $this->updateCacheByKey($cacheKey, $results, $scraperResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $results;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds cached scraper results by id in the database, if not found scrapes them from MAL.
|
||||||
|
* @param int $id
|
||||||
|
* @param string $cacheKey
|
||||||
|
* @return CachedData
|
||||||
|
* @throws NotFoundHttpException
|
||||||
|
*/
|
||||||
|
public function find(int $id, string $cacheKey): CachedData
|
||||||
|
{
|
||||||
|
$results = CachedData::from($this->repository->getAllByMalId($id));
|
||||||
|
|
||||||
|
if ($results->isEmpty() || $results->isExpired()) {
|
||||||
|
$response = $this->repository->scrape($id);
|
||||||
|
|
||||||
|
$this->raiseNotFoundIfErrors($response);
|
||||||
|
|
||||||
|
$results = $this->updateCacheById($id, $cacheKey, $results, $response);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->raiseNotFoundIfEmpty($results);
|
||||||
|
|
||||||
|
return $results;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findByKey(string $key, mixed $val, string $cacheKey): CachedData
|
||||||
|
{
|
||||||
|
$results = CachedData::from($this->repository->where($key, $val)->get());
|
||||||
|
|
||||||
|
if ($results->isEmpty() || $results->isExpired()) {
|
||||||
|
$scraperResponse = $this->repository->scrape($key);
|
||||||
|
|
||||||
|
$this->raiseNotFoundIfErrors($scraperResponse);
|
||||||
|
|
||||||
|
$response = $this->prepareScraperResponse($cacheKey, $results->isEmpty(), $scraperResponse);
|
||||||
|
$response->offsetSet($key, $val);
|
||||||
|
|
||||||
|
if ($results->isEmpty()) {
|
||||||
|
$this->repository->insert($response->toArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($results->isExpired()) {
|
||||||
|
$this->repository->where($key, $val)->update($response->toArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
$results = CachedData::from($this->repository->where($key, $val)->get());
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->raiseNotFoundIfEmpty($results);
|
||||||
|
|
||||||
|
return $results;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get(string $cacheKey): CachedData
|
||||||
|
{
|
||||||
|
return CachedData::from($this->getByCacheKey($cacheKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function augmentResponse(JsonResponse|Response $response, string $cacheKey, CachedData $scraperResults): JsonResponse|Response
|
||||||
|
{
|
||||||
|
return $response
|
||||||
|
->header("X-Request-Fingerprint", $cacheKey)
|
||||||
|
->setTtl($this->cacheTtl())
|
||||||
|
->setExpires(Carbon::createFromTimestamp($scraperResults->expiry()))
|
||||||
|
->setLastModified(Carbon::createFromTimestamp($scraperResults->lastModified()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function raiseNotFoundIfEmpty(CachedData $results)
|
||||||
|
{
|
||||||
|
if ($results->isEmpty()) {
|
||||||
|
abort(404, "Resource not found.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function raiseNotFoundIfErrors(mixed $response)
|
||||||
|
{
|
||||||
|
if (HttpHelper::hasError($response)) {
|
||||||
|
abort(404, "Resource not found.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function updateCacheById(int $id, string $cacheKey, CachedData $results, array $scraperResponse): CachedData
|
||||||
|
{
|
||||||
|
$response = $this->prepareScraperResponse($cacheKey, $results->isEmpty(), $scraperResponse);
|
||||||
|
|
||||||
|
if ($results->isEmpty()) {
|
||||||
|
$this->repository->insert($response->toArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($results->isExpired()) {
|
||||||
|
$this->repository->queryByMalId($id)->update($response->toArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
return new CachedData(collect($this->repository->getAllByMalId($id)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function updateCacheByKey(string $cacheKey, CachedData $results, array $scraperResponse): CachedData
|
||||||
|
{
|
||||||
|
$response = $this->prepareScraperResponse($cacheKey, $results->isEmpty(), $scraperResponse);
|
||||||
|
|
||||||
|
// insert cache if resource doesn't exist
|
||||||
|
if ($results->isEmpty()) {
|
||||||
|
$this->repository->insert($response->toArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($results->isExpired()) {
|
||||||
|
$this->getQueryableByCacheKey($cacheKey)->update($response->toArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
return new CachedData($this->getByCacheKey($cacheKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function prepareScraperResponse(string $cacheKey, bool $resultsEmpty, array $scraperResponse): CachedData
|
||||||
|
{
|
||||||
|
$meta = [];
|
||||||
|
if ($resultsEmpty) {
|
||||||
|
$meta = [
|
||||||
|
'createdAt' => new UTCDateTime(),
|
||||||
|
'request_hash' => $cacheKey
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update `modifiedAt` meta
|
||||||
|
$meta['modifiedAt'] = new UTCDateTime();
|
||||||
|
|
||||||
|
// join meta data with response
|
||||||
|
return new CachedData(collect($meta + $scraperResponse));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getByCacheKey(string $cacheKey): Collection
|
||||||
|
{
|
||||||
|
return $this->getQueryableByCacheKey($cacheKey)->get();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getQueryableByCacheKey(string $cacheKey): Builder
|
||||||
|
{
|
||||||
|
return $this->repository->where("request_hash", $cacheKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function serializeScraperResult($data): array
|
||||||
|
{
|
||||||
|
return $this->serializer->toArray($data);
|
||||||
|
}
|
||||||
|
}
|
86
app/Support/CachedData.php
Normal file
86
app/Support/CachedData.php
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Support;
|
||||||
|
|
||||||
|
use App\Concerns\ScraperCacheTtl;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
|
final class CachedData
|
||||||
|
{
|
||||||
|
use ScraperCacheTtl;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private readonly Collection $scraperResult
|
||||||
|
)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function from(Collection $scraperResult): self
|
||||||
|
{
|
||||||
|
return new self($scraperResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function collect(): Collection
|
||||||
|
{
|
||||||
|
return $this->scraperResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function offsetGet(string $key): mixed
|
||||||
|
{
|
||||||
|
return $this->scraperResult->offsetGet($key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function offsetSet(string $key, mixed $value): void
|
||||||
|
{
|
||||||
|
$this->scraperResult->offsetSet($key, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isEmpty(): bool
|
||||||
|
{
|
||||||
|
return $this->scraperResult->isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isExpired(): bool
|
||||||
|
{
|
||||||
|
$lastModified = $this->lastModified();
|
||||||
|
|
||||||
|
if ($lastModified === null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$expiry = $this->expiry();
|
||||||
|
|
||||||
|
return time() > $expiry;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toArray(): array
|
||||||
|
{
|
||||||
|
return $this->scraperResult->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function expiry(): int
|
||||||
|
{
|
||||||
|
$modifiedAt = $this->lastModified();
|
||||||
|
$ttl = $this->cacheTtl();
|
||||||
|
return $modifiedAt !== null ? $ttl + $modifiedAt : $ttl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function lastModified(): ?int
|
||||||
|
{
|
||||||
|
if ($this->scraperResult->isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $this->scraperResult->first();
|
||||||
|
|
||||||
|
if (is_array($result)) {
|
||||||
|
return (int) $result["modifiedAt"]->toDateTime()->format("U");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_object($result)) {
|
||||||
|
return $result->modifiedAt->toDateTime()->format("U");
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -15,6 +15,7 @@ use App\Contracts\UnitOfWork;
|
|||||||
use App\Contracts\UserRepository;
|
use App\Contracts\UserRepository;
|
||||||
use App\Repositories\AnimeGenresRepository;
|
use App\Repositories\AnimeGenresRepository;
|
||||||
use App\Repositories\DatabaseRepository;
|
use App\Repositories\DatabaseRepository;
|
||||||
|
use App\Repositories\DocumentRepository;
|
||||||
use App\Repositories\MangaGenresRepository;
|
use App\Repositories\MangaGenresRepository;
|
||||||
|
|
||||||
final class JikanUnitOfWork implements UnitOfWork
|
final class JikanUnitOfWork implements UnitOfWork
|
||||||
@ -93,4 +94,9 @@ final class JikanUnitOfWork implements UnitOfWork
|
|||||||
{
|
{
|
||||||
return new DatabaseRepository(fn () => $modelClass::query(), fn ($x, $y) => $modelClass::search($x, $y));
|
return new DatabaseRepository(fn () => $modelClass::query(), fn ($x, $y) => $modelClass::search($x, $y));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function documents(string $tableName): DocumentRepository
|
||||||
|
{
|
||||||
|
return new DocumentRepository($tableName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,13 +3,13 @@
|
|||||||
namespace App\Support;
|
namespace App\Support;
|
||||||
|
|
||||||
use App\Contracts\RepositoryQuery as RepositoryQueryContract;
|
use App\Contracts\RepositoryQuery as RepositoryQueryContract;
|
||||||
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
use Illuminate\Contracts\Database\Query\Builder;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Laravel\Scout\Builder as ScoutBuilder;
|
use Laravel\Scout\Builder as ScoutBuilder;
|
||||||
|
|
||||||
class RepositoryQuery extends RepositoryQueryBase implements RepositoryQueryContract
|
class RepositoryQuery extends RepositoryQueryBase implements RepositoryQueryContract
|
||||||
{
|
{
|
||||||
public function filter(Collection $params): EloquentBuilder|ScoutBuilder
|
public function filter(Collection $params): Builder|ScoutBuilder
|
||||||
{
|
{
|
||||||
return $this->queryable()->filter($params);
|
return $this->queryable()->filter($params);
|
||||||
}
|
}
|
||||||
@ -18,4 +18,9 @@ class RepositoryQuery extends RepositoryQueryBase implements RepositoryQueryCont
|
|||||||
{
|
{
|
||||||
return $this->searchable($keywords, $callback);
|
return $this->searchable($keywords, $callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function where(string $key, mixed $value): Builder
|
||||||
|
{
|
||||||
|
return $this->queryable()->where($key, $value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
namespace App\Support;
|
namespace App\Support;
|
||||||
use Laravel\Scout\Builder as ScoutBuilder;
|
use Laravel\Scout\Builder as ScoutBuilder;
|
||||||
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
use Illuminate\Contracts\Database\Query\Builder;
|
||||||
|
|
||||||
class RepositoryQueryBase
|
class RepositoryQueryBase
|
||||||
{
|
{
|
||||||
private ?EloquentBuilder $queryableBuilder;
|
private ?Builder $queryableBuilder;
|
||||||
private ?ScoutBuilder $searchableBuilder;
|
private ?ScoutBuilder $searchableBuilder;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
@ -15,7 +15,7 @@ class RepositoryQueryBase
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function queryable(bool $createNew = false): EloquentBuilder
|
protected function queryable(bool $createNew = false): Builder
|
||||||
{
|
{
|
||||||
if ($createNew) {
|
if ($createNew) {
|
||||||
$callback = $this->getQueryable;
|
$callback = $this->getQueryable;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user