mirror of
https://github.com/jikan-me/jikan-rest.git
synced 2025-02-20 11:23:35 +08:00
wip - major refactor
- AppServiceProvider is needs more work to wire in new services - todo: more dtos - todo: add unit tests - todo: add more integration tests
This commit is contained in:
parent
30fc65c6c4
commit
a145f18bbd
@ -2,7 +2,12 @@
|
||||
|
||||
namespace App;
|
||||
|
||||
use App\Concerns\FilteredByLetter;
|
||||
use App\Concerns\MediaFilters;
|
||||
use App\Enums\AnimeRatingEnum;
|
||||
use App\Enums\AnimeTypeEnum;
|
||||
use App\Http\HttpHelper;
|
||||
use Carbon\CarbonImmutable;
|
||||
use Database\Factories\AnimeFactory;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Jikan\Jikan;
|
||||
@ -11,11 +16,9 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
|
||||
class Anime extends JikanApiSearchableModel
|
||||
{
|
||||
use HasFactory;
|
||||
use HasFactory, MediaFilters, FilteredByLetter;
|
||||
|
||||
// note that here we skip "score", "min_score", "max_score", "rating" and others because they need special logic
|
||||
// to set the correct filtering on the ORM.
|
||||
protected array $filters = ["order_by", "status", "type", "sort"];
|
||||
protected array $filters = ["order_by", "status", "type", "sort", "max_score", "min_score", "score", "rating", "start_date", "end_date", "producer", "producers", "letter"];
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
@ -25,6 +28,8 @@ class Anime extends JikanApiSearchableModel
|
||||
'mal_id','url','title','title_english','title_japanese','title_synonyms', 'titles', 'images', 'type','source','episodes','status','airing','aired','duration','rating','score','scored_by','rank','popularity','members','favorites','synopsis','background','premiered','broadcast','related','producers','licensors','studios','genres', 'explicit_genres', 'themes', 'demographics', 'opening_themes','ending_themes'
|
||||
];
|
||||
|
||||
protected ?string $displayNameFieldName = "title";
|
||||
|
||||
/**
|
||||
* The accessors to append to the model's array form.
|
||||
*
|
||||
@ -124,6 +129,68 @@ class Anime extends JikanApiSearchableModel
|
||||
];
|
||||
}
|
||||
|
||||
/** @noinspection PhpUnused */
|
||||
public function filterByLetter(\Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder $query, string $value): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder
|
||||
{
|
||||
return $query->where("title", "like", "{$value}%");
|
||||
}
|
||||
|
||||
/** @noinspection PhpUnused */
|
||||
public function filterByType(\Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder $query, AnimeTypeEnum $value): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder
|
||||
{
|
||||
return $query->where("type", $value->label);
|
||||
}
|
||||
|
||||
/** @noinspection PhpUnused */
|
||||
public function filterByRating(\Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder $query, AnimeRatingEnum $value): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder
|
||||
{
|
||||
return $query->where("rating", $value->label);
|
||||
}
|
||||
|
||||
/** @noinspection PhpUnused */
|
||||
public function filterByStartDate(\Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder $query, CarbonImmutable $date): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder
|
||||
{
|
||||
return $query->where("aired.from", $date->setTime(0, 0)->toAtomString());
|
||||
}
|
||||
|
||||
/** @noinspection PhpUnused */
|
||||
public function filterByEndDate(\Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder $query, CarbonImmutable $date): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder
|
||||
{
|
||||
return $query->where("aired.to", $date->setTime(0, 0)->toAtomString());
|
||||
}
|
||||
|
||||
public function filterByProducer(\Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder $query, string $value): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder
|
||||
{
|
||||
if (empty($value)) {
|
||||
return $query;
|
||||
}
|
||||
|
||||
$producer = (int)$value;
|
||||
return $query
|
||||
->orWhere('producers.mal_id', $producer)
|
||||
->orWhere('licensors.mal_id', $producer)
|
||||
->orWhere('studios.mal_id', $producer);
|
||||
}
|
||||
|
||||
/** @noinspection PhpUnused */
|
||||
public function filterByProducers(\Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder $query, string $value): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder
|
||||
{
|
||||
if (empty($value)) {
|
||||
return $query;
|
||||
}
|
||||
|
||||
$producers = explode(',', $value);
|
||||
foreach ($producers as $producer) {
|
||||
if (empty($producer)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$query = $this->filterByProducer($query, $value);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
public static function scrape(int $id)
|
||||
{
|
||||
$data = app('JikanParser')->getAnime(new AnimeRequest($id));
|
||||
|
37
app/Casts/EnumCast.php
Normal file
37
app/Casts/EnumCast.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Casts;
|
||||
|
||||
use BackedEnum;
|
||||
use Spatie\LaravelData\Casts\Cast;
|
||||
use Spatie\LaravelData\Casts\Uncastable;
|
||||
use Spatie\LaravelData\Exceptions\CannotCastEnum;
|
||||
use Spatie\LaravelData\Support\DataProperty;
|
||||
use Throwable;
|
||||
|
||||
class EnumCast implements Cast
|
||||
{
|
||||
public function __construct(
|
||||
protected ?string $type = null
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws CannotCastEnum
|
||||
*/
|
||||
public function cast(DataProperty $property, mixed $value, array $context): mixed
|
||||
{
|
||||
$type = $this->type ?? $property->type->findAcceptedTypeForBaseType(BackedEnum::class);
|
||||
|
||||
if ($type === null) {
|
||||
return Uncastable::create();
|
||||
}
|
||||
|
||||
try {
|
||||
/** @noinspection PhpUndefinedMethodInspection */
|
||||
return $type::from($value);
|
||||
} catch (Throwable $e) {
|
||||
throw CannotCastEnum::create($type, $value);
|
||||
}
|
||||
}
|
||||
}
|
@ -2,15 +2,16 @@
|
||||
|
||||
namespace App;
|
||||
|
||||
use App\Concerns\FilteredByLetter;
|
||||
use Jikan\Jikan;
|
||||
use Jikan\Request\Character\CharacterRequest;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
|
||||
class Character extends JikanApiSearchableModel
|
||||
{
|
||||
use HasFactory;
|
||||
use HasFactory, FilteredByLetter;
|
||||
|
||||
protected array $filters = ["order_by", "sort"];
|
||||
protected array $filters = ["order_by", "sort", "letter"];
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
@ -28,6 +29,8 @@ class Character extends JikanApiSearchableModel
|
||||
*/
|
||||
protected $appends = ['images', 'favorites'];
|
||||
|
||||
protected ?string $displayNameFieldName = "name";
|
||||
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
@ -44,6 +47,7 @@ class Character extends JikanApiSearchableModel
|
||||
'_id', 'trailer_url', 'premiered', 'opening_themes', 'ending_themes', 'images', 'member_favorites'
|
||||
];
|
||||
|
||||
/** @noinspection PhpUnused */
|
||||
public function getFavoritesAttribute()
|
||||
{
|
||||
return $this->attributes['member_favorites'];
|
||||
|
21
app/Club.php
21
app/Club.php
@ -2,11 +2,16 @@
|
||||
|
||||
namespace App;
|
||||
|
||||
use App\Concerns\FilteredByLetter;
|
||||
use App\Enums\ClubCategoryEnum;
|
||||
use App\Enums\ClubTypeEnum;
|
||||
use Jikan\Request\Club\ClubRequest;
|
||||
|
||||
class Club extends JikanApiSearchableModel
|
||||
{
|
||||
protected array $filters = ["order_by", "sort"];
|
||||
use FilteredByLetter;
|
||||
|
||||
protected array $filters = ["order_by", "sort", "letter", "category", "type"];
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
@ -24,6 +29,8 @@ class Club extends JikanApiSearchableModel
|
||||
*/
|
||||
protected $appends = ['images'];
|
||||
|
||||
protected ?string $displayNameFieldName = "title";
|
||||
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
@ -40,6 +47,18 @@ class Club extends JikanApiSearchableModel
|
||||
'_id', 'request_hash', 'expiresAt', 'images'
|
||||
];
|
||||
|
||||
/** @noinspection PhpUnused */
|
||||
public function filterByCategory(\Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder $query, ClubCategoryEnum $value): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder
|
||||
{
|
||||
return $query->where("category", $value->label);
|
||||
}
|
||||
|
||||
/** @noinspection PhpUnused */
|
||||
public function filterByType(\Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder $query, ClubTypeEnum $value): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder
|
||||
{
|
||||
return $query->where("access", $value->label);
|
||||
}
|
||||
|
||||
public static function scrape(int $id)
|
||||
{
|
||||
$data = app('JikanParser')->getClub(new ClubRequest($id));
|
||||
|
33
app/Concerns/FilteredByLetter.php
Normal file
33
app/Concerns/FilteredByLetter.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Concerns;
|
||||
|
||||
trait FilteredByLetter
|
||||
{
|
||||
/**
|
||||
* The name of the field which contains the display name of the record.
|
||||
* @var ?string
|
||||
*/
|
||||
protected ?string $displayNameFieldName;
|
||||
|
||||
/** @noinspection PhpUnused */
|
||||
public function filterByLetter(\Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder $query, string $value): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder
|
||||
{
|
||||
if (empty($this->displayNameFieldName)) {
|
||||
return $query;
|
||||
}
|
||||
return $query->where($this->displayNameFieldName, "like", "{$value}%");
|
||||
}
|
||||
|
||||
public function getDisplayNameFieldName(): string
|
||||
{
|
||||
return $this->displayNameFieldName;
|
||||
}
|
||||
|
||||
public function displayNameFieldName(string $name): self
|
||||
{
|
||||
$this->displayNameFieldName = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
28
app/Concerns/HasRequestFingerprint.php
Normal file
28
app/Concerns/HasRequestFingerprint.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Concerns;
|
||||
|
||||
use App\Http\HttpHelper;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
/**
|
||||
* Helper trait for data transfer objects
|
||||
*
|
||||
* Ref: https://spatie.be/docs/laravel-data/v2/as-a-data-transfer-object/request-to-data-object#content-mapping-a-request-onto-a-data-object
|
||||
*/
|
||||
trait HasRequestFingerprint
|
||||
{
|
||||
protected ?string $fingerprint;
|
||||
|
||||
public static function fromRequest(Request $request): ?static
|
||||
{
|
||||
$result = new self();
|
||||
$result->fingerprint = HttpHelper::resolveRequestFingerprint($request);
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getFingerPrint(): string
|
||||
{
|
||||
return $this->fingerprint;
|
||||
}
|
||||
}
|
65
app/Concerns/MediaFilters.php
Normal file
65
app/Concerns/MediaFilters.php
Normal file
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace App\Concerns;
|
||||
|
||||
use Spatie\Enum\Enum;
|
||||
|
||||
trait MediaFilters
|
||||
{
|
||||
public function filterByMaxScore(\Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder $query, mixed $value): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder
|
||||
{
|
||||
return $query->where("score", "<=", floatval($value));
|
||||
}
|
||||
|
||||
public function filterByMinScore(\Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder $query, mixed $value): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder
|
||||
{
|
||||
return $query->where("score", ">=", floatval($value));
|
||||
}
|
||||
|
||||
public function filterByScore(\Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder $query, mixed $value): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder
|
||||
{
|
||||
return $query->where("score", floatval($value));
|
||||
}
|
||||
|
||||
public function filterByStatus(\Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder $query, Enum $value): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder
|
||||
{
|
||||
return $query->where("status", $value->label);
|
||||
}
|
||||
|
||||
public function filterByGenres(\Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder $query, mixed $value): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder
|
||||
{
|
||||
if (!is_string($value) || empty($value)) {
|
||||
return $query;
|
||||
}
|
||||
$genres = explode(',', $value);
|
||||
|
||||
foreach ($genres as $genreItem) {
|
||||
$genre = (int) $genreItem;
|
||||
$query = $query->orWhere('genres.mal_id', $genre)
|
||||
->orWhere('demographics.mal_id', $genre)
|
||||
->orWhere('themes.mal_id', $genre)
|
||||
->orWhere('explicit_genres.mal_id', $genre);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
public function filterByGenresExclude(\Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder $query, mixed $value): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder
|
||||
{
|
||||
if (!is_string($value) || empty($value)) {
|
||||
return $query;
|
||||
}
|
||||
$genres = explode(',', $value);
|
||||
|
||||
foreach ($genres as $genreItem) {
|
||||
$genre = (int) $genreItem;
|
||||
$query = $query
|
||||
->where('genres.mal_id', '!=', $genre)
|
||||
->where('demographics.mal_id', '!=', $genre)
|
||||
->where('themes.mal_id', '!=', $genre)
|
||||
->where('explicit_genres.mal_id', '!=', $genre);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
}
|
15
app/Concerns/ResolvesPaginatorParams.php
Normal file
15
app/Concerns/ResolvesPaginatorParams.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Concerns;
|
||||
|
||||
trait ResolvesPaginatorParams
|
||||
{
|
||||
private function getPaginatorParams(?int $limit = null, ?int $page = null): array
|
||||
{
|
||||
$default_max_results_per_page = env('MAX_RESULTS_PER_PAGE', 25);
|
||||
$limit = $limit ?? $default_max_results_per_page;
|
||||
$page = $page ?? 1;
|
||||
|
||||
return compact($limit, $page);
|
||||
}
|
||||
}
|
186
app/Concerns/ScraperResultCache.php
Normal file
186
app/Concerns/ScraperResultCache.php
Normal file
@ -0,0 +1,186 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
25
app/Contracts/AnimeRepository.php
Normal file
25
app/Contracts/AnimeRepository.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Contracts;
|
||||
|
||||
use App\Anime;
|
||||
use \Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
||||
use \Laravel\Scout\Builder as ScoutBuilder;
|
||||
|
||||
/**
|
||||
* @implements Repository<Anime>
|
||||
*/
|
||||
interface AnimeRepository extends Repository
|
||||
{
|
||||
public function getTopAiringItems(): EloquentBuilder|ScoutBuilder;
|
||||
|
||||
public function getTopUpcomingItems(): EloquentBuilder|ScoutBuilder;
|
||||
|
||||
public function exceptItemsWithAdultRating(): EloquentBuilder|ScoutBuilder;
|
||||
|
||||
public function orderByPopularity(): EloquentBuilder|ScoutBuilder;
|
||||
|
||||
public function orderByFavoriteCount(): EloquentBuilder|ScoutBuilder;
|
||||
|
||||
public function orderByRank(): EloquentBuilder|ScoutBuilder;
|
||||
}
|
15
app/Contracts/CharacterRepository.php
Normal file
15
app/Contracts/CharacterRepository.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Contracts;
|
||||
|
||||
use App\Character;
|
||||
use \Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
||||
use \Laravel\Scout\Builder as ScoutBuilder;
|
||||
|
||||
/**
|
||||
* @implements Repository<Character>
|
||||
*/
|
||||
interface CharacterRepository extends Repository
|
||||
{
|
||||
public function topCharacters(): EloquentBuilder|ScoutBuilder;
|
||||
}
|
12
app/Contracts/ClubRepository.php
Normal file
12
app/Contracts/ClubRepository.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Contracts;
|
||||
|
||||
use App\Club;
|
||||
|
||||
/**
|
||||
* @implements Repository<Club>
|
||||
*/
|
||||
interface ClubRepository extends Repository
|
||||
{
|
||||
}
|
15
app/Contracts/DataRequest.php
Normal file
15
app/Contracts/DataRequest.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Contracts;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||
use Illuminate\Http\Response;
|
||||
|
||||
/**
|
||||
* Marker interface to represent a request with a response
|
||||
* @template T of ResourceCollection|JsonResource|Response
|
||||
*/
|
||||
interface DataRequest
|
||||
{
|
||||
}
|
18
app/Contracts/GenreRepository.php
Normal file
18
app/Contracts/GenreRepository.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Contracts;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
interface GenreRepository extends Repository
|
||||
{
|
||||
public function genres(): Collection;
|
||||
|
||||
public function getExplicitItems(): Collection;
|
||||
|
||||
public function getThemes(): Collection;
|
||||
|
||||
public function getDemographics(): Collection;
|
||||
|
||||
public function all(): Collection;
|
||||
}
|
12
app/Contracts/MagazineRepository.php
Normal file
12
app/Contracts/MagazineRepository.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Contracts;
|
||||
|
||||
use App\Magazine;
|
||||
|
||||
/**
|
||||
* @implements Repository<Magazine>
|
||||
*/
|
||||
interface MagazineRepository extends Repository
|
||||
{
|
||||
}
|
23
app/Contracts/MangaRepository.php
Normal file
23
app/Contracts/MangaRepository.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Contracts;
|
||||
|
||||
use App\Manga;
|
||||
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
||||
use Laravel\Scout\Builder as ScoutBuilder;
|
||||
|
||||
/**
|
||||
* @implements Repository<Manga>
|
||||
*/
|
||||
interface MangaRepository extends Repository
|
||||
{
|
||||
public function getTopPublishingItems(): EloquentBuilder|ScoutBuilder;
|
||||
|
||||
public function getTopUpcomingItems(): EloquentBuilder|ScoutBuilder;
|
||||
|
||||
public function orderByPopularity(): EloquentBuilder|ScoutBuilder;
|
||||
|
||||
public function orderByFavoriteCount(): EloquentBuilder|ScoutBuilder;
|
||||
|
||||
public function orderByRank(): EloquentBuilder|ScoutBuilder;
|
||||
}
|
19
app/Contracts/Mediator.php
Normal file
19
app/Contracts/Mediator.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
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
|
||||
{
|
||||
/**
|
||||
* Send a request to a single handler
|
||||
* @template T
|
||||
* @param DataRequest<T> $requestData
|
||||
* @return T
|
||||
*/
|
||||
public function send(DataRequest $requestData);
|
||||
}
|
15
app/Contracts/PeopleRepository.php
Normal file
15
app/Contracts/PeopleRepository.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Contracts;
|
||||
|
||||
use App\Person;
|
||||
use \Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
||||
use \Laravel\Scout\Builder as ScoutBuilder;
|
||||
|
||||
/**
|
||||
* @implements Repository<Person>
|
||||
*/
|
||||
interface PeopleRepository extends Repository
|
||||
{
|
||||
public function topPeople(): EloquentBuilder|ScoutBuilder;
|
||||
}
|
13
app/Contracts/ProducerRepository.php
Normal file
13
app/Contracts/ProducerRepository.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Contracts;
|
||||
|
||||
use App\Producers;
|
||||
|
||||
/**
|
||||
* @implements Repository<Producers>
|
||||
*/
|
||||
interface ProducerRepository extends Repository
|
||||
{
|
||||
|
||||
}
|
36
app/Contracts/Repository.php
Normal file
36
app/Contracts/Repository.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Contracts;
|
||||
|
||||
use App\JikanApiModel;
|
||||
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* @template T of JikanApiModel
|
||||
* @implements RepositoryQuery<T>
|
||||
*/
|
||||
interface Repository extends RepositoryQuery
|
||||
{
|
||||
/**
|
||||
* @return T
|
||||
*/
|
||||
public function createEntity();
|
||||
|
||||
/**
|
||||
* @return ?T
|
||||
*/
|
||||
public function getByMalId(int $id);
|
||||
|
||||
public function getAllByMalId(int $id): Collection;
|
||||
|
||||
public function queryByMalId(int $id): EloquentBuilder;
|
||||
|
||||
public function tableName(): string;
|
||||
|
||||
// fixme: this should not be here.
|
||||
// this is here because we have the "scrape" static method on models
|
||||
public function scrape(int $id): array;
|
||||
|
||||
public function insert(array $attributes): bool;
|
||||
}
|
27
app/Contracts/RepositoryQuery.php
Normal file
27
app/Contracts/RepositoryQuery.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Contracts;
|
||||
|
||||
use App\JikanApiModel;
|
||||
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
||||
use Illuminate\Support\Collection;
|
||||
use Laravel\Scout\Builder as ScoutBuilder;
|
||||
|
||||
/**
|
||||
* @template T of JikanApiModel
|
||||
*/
|
||||
interface RepositoryQuery
|
||||
{
|
||||
/**
|
||||
* @param Collection $params
|
||||
* @return EloquentBuilder<T>|ScoutBuilder<T>
|
||||
*/
|
||||
public function filter(Collection $params): EloquentBuilder|ScoutBuilder;
|
||||
|
||||
/**
|
||||
* @param string $keywords
|
||||
* @param \Closure|null $callback
|
||||
* @return ScoutBuilder<T>
|
||||
*/
|
||||
public function search(string $keywords, ?\Closure $callback = null): ScoutBuilder;
|
||||
}
|
25
app/Contracts/RequestHandler.php
Normal file
25
app/Contracts/RequestHandler.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Contracts;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||
use Illuminate\Http\Response;
|
||||
|
||||
/**
|
||||
* @template TRequest of DataRequest<TResponse>
|
||||
* @template TResponse of ResourceCollection|JsonResource|Response
|
||||
*/
|
||||
interface RequestHandler
|
||||
{
|
||||
/**
|
||||
* @param TRequest $request
|
||||
* @return TResponse
|
||||
*/
|
||||
public function handle($request);
|
||||
|
||||
/**
|
||||
* @return class-string<TRequest>
|
||||
*/
|
||||
public function requestClass(): string;
|
||||
}
|
26
app/Contracts/UnitOfWork.php
Normal file
26
app/Contracts/UnitOfWork.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Contracts;
|
||||
|
||||
interface UnitOfWork
|
||||
{
|
||||
public function anime(): AnimeRepository;
|
||||
|
||||
public function manga(): MangaRepository;
|
||||
|
||||
public function characters(): CharacterRepository;
|
||||
|
||||
public function people(): PeopleRepository;
|
||||
|
||||
public function clubs(): ClubRepository;
|
||||
|
||||
public function producers(): ProducerRepository;
|
||||
|
||||
public function magazines(): MagazineRepository;
|
||||
|
||||
public function users(): UserRepository;
|
||||
|
||||
public function animeGenres(): GenreRepository;
|
||||
|
||||
public function mangaGenres(): GenreRepository;
|
||||
}
|
12
app/Contracts/UserRepository.php
Normal file
12
app/Contracts/UserRepository.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Contracts;
|
||||
|
||||
use App\Profile;
|
||||
|
||||
/**
|
||||
* @implements Repository<Profile>
|
||||
*/
|
||||
interface UserRepository extends Repository
|
||||
{
|
||||
}
|
13
app/Dto/AnimeGenreListCommand.php
Normal file
13
app/Dto/AnimeGenreListCommand.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Dto;
|
||||
|
||||
use App\Contracts\DataRequest;
|
||||
use App\Http\Resources\V4\GenreCollection;
|
||||
|
||||
/**
|
||||
* @implements DataRequest<GenreCollection>
|
||||
*/
|
||||
final class AnimeGenreListCommand extends GenreListCommand implements DataRequest
|
||||
{
|
||||
}
|
50
app/Dto/AnimeSearchCommand.php
Normal file
50
app/Dto/AnimeSearchCommand.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Dto;
|
||||
|
||||
use App\Contracts\DataRequest;
|
||||
use App\Enums\AnimeOrderByEnum;
|
||||
use App\Enums\AnimeRatingEnum;
|
||||
use App\Enums\AnimeStatusEnum;
|
||||
use App\Http\Resources\V4\AnimeCollection;
|
||||
use Spatie\Enum\Laravel\Rules\EnumRule;
|
||||
use Spatie\LaravelData\Attributes\MapInputName;
|
||||
use Spatie\LaravelData\Attributes\MapOutputName;
|
||||
use Spatie\LaravelData\Attributes\Validation\IntegerType;
|
||||
use Spatie\LaravelData\Attributes\Validation\Min;
|
||||
use Spatie\LaravelData\Attributes\Validation\Prohibits;
|
||||
use Spatie\LaravelData\Attributes\Validation\StringType;
|
||||
use Spatie\LaravelData\Attributes\WithCast;
|
||||
use App\Casts\EnumCast;
|
||||
use Spatie\LaravelData\Optional;
|
||||
|
||||
/**
|
||||
* @implements DataRequest<AnimeCollection>
|
||||
*/
|
||||
final class AnimeSearchCommand extends MediaSearchCommand implements DataRequest
|
||||
{
|
||||
#[WithCast(EnumCast::class, AnimeStatusEnum::class)]
|
||||
public AnimeStatusEnum|Optional $status;
|
||||
|
||||
#[WithCast(EnumCast::class, AnimeRatingEnum::class)]
|
||||
public AnimeRatingEnum|Optional $rating;
|
||||
|
||||
#[IntegerType, Min(1)]
|
||||
public int|Optional $producer;
|
||||
|
||||
#[Prohibits("producer"), StringType]
|
||||
public string|Optional $producers;
|
||||
|
||||
#[MapInputName("order_by"), MapOutputName("order_by"), WithCast(EnumCast::class, AnimeOrderByEnum::class)]
|
||||
public AnimeOrderByEnum|Optional $orderBy;
|
||||
|
||||
public static function rules(): array
|
||||
{
|
||||
return [
|
||||
...parent::rules(),
|
||||
"status" => [new EnumRule(AnimeStatusEnum::class)],
|
||||
"rating" => [new EnumRule(AnimeRatingEnum::class)],
|
||||
"order_by" => [new EnumRule(AnimeOrderByEnum::class)]
|
||||
];
|
||||
}
|
||||
}
|
29
app/Dto/CharactersSearchCommand.php
Normal file
29
app/Dto/CharactersSearchCommand.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Dto;
|
||||
|
||||
use App\Casts\EnumCast;
|
||||
use App\Contracts\DataRequest;
|
||||
use App\Enums\CharacterOrderByEnum;
|
||||
use App\Http\Resources\V4\CharacterCollection;
|
||||
use Illuminate\Support\Optional;
|
||||
use Spatie\Enum\Laravel\Rules\EnumRule;
|
||||
use Spatie\LaravelData\Attributes\MapInputName;
|
||||
use Spatie\LaravelData\Attributes\MapOutputName;
|
||||
use Spatie\LaravelData\Attributes\WithCast;
|
||||
|
||||
/**
|
||||
* @implements DataRequest<CharacterCollection>
|
||||
*/
|
||||
final class CharactersSearchCommand extends SearchCommand implements DataRequest
|
||||
{
|
||||
#[MapInputName("order_by"), MapOutputName("order_by"), WithCast(EnumCast::class, CharacterOrderByEnum::class)]
|
||||
public CharacterOrderByEnum|Optional $orderBy;
|
||||
|
||||
public static function rules(): array
|
||||
{
|
||||
return [
|
||||
"order_by" => [new EnumRule(CharacterOrderByEnum::class)]
|
||||
];
|
||||
}
|
||||
}
|
40
app/Dto/ClubSearchCommand.php
Normal file
40
app/Dto/ClubSearchCommand.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Dto;
|
||||
|
||||
use App\Casts\EnumCast;
|
||||
use App\Contracts\DataRequest;
|
||||
use App\Enums\ClubCategoryEnum;
|
||||
use App\Enums\ClubOrderByEnum;
|
||||
use App\Enums\ClubTypeEnum;
|
||||
use App\Http\Resources\V4\ClubCollection;
|
||||
use Illuminate\Support\Optional;
|
||||
use Spatie\Enum\Laravel\Rules\EnumRule;
|
||||
use Spatie\LaravelData\Attributes\MapInputName;
|
||||
use Spatie\LaravelData\Attributes\MapOutputName;
|
||||
use Spatie\LaravelData\Attributes\WithCast;
|
||||
|
||||
/**
|
||||
* @implements DataRequest<ClubCollection>
|
||||
*/
|
||||
final class ClubSearchCommand extends SearchCommand implements DataRequest
|
||||
{
|
||||
#[WithCast(EnumCast::class, ClubCategoryEnum::class)]
|
||||
public ClubCategoryEnum|Optional $category;
|
||||
|
||||
#[WithCast(EnumCast::class, ClubTypeEnum::class)]
|
||||
public ClubTypeEnum|Optional $type;
|
||||
|
||||
#[MapInputName("order_by"), MapOutputName("order_by"), WithCast(EnumCast::class, ClubOrderByEnum::class)]
|
||||
public ClubOrderByEnum|Optional $orderBy;
|
||||
|
||||
public static function rules(): array
|
||||
{
|
||||
return [
|
||||
...parent::rules(),
|
||||
"category" => [new EnumRule(ClubCategoryEnum::class)],
|
||||
"type" => [new EnumRule(ClubTypeEnum::class)],
|
||||
"order_by" => [new EnumRule(ClubOrderByEnum::class)]
|
||||
];
|
||||
}
|
||||
}
|
23
app/Dto/GenreListCommand.php
Normal file
23
app/Dto/GenreListCommand.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Dto;
|
||||
|
||||
use App\Enums\GenreFilterEnum;
|
||||
use Spatie\Enum\Laravel\Rules\EnumRule;
|
||||
use Spatie\LaravelData\Attributes\WithCast;
|
||||
use App\Casts\EnumCast;
|
||||
use Spatie\LaravelData\Data;
|
||||
use Spatie\LaravelData\Optional;
|
||||
|
||||
abstract class GenreListCommand extends Data
|
||||
{
|
||||
#[WithCast(EnumCast::class, GenreFilterEnum::class)]
|
||||
public GenreFilterEnum|Optional $filter;
|
||||
|
||||
public static function rules(): array
|
||||
{
|
||||
return [
|
||||
"filter" => [new EnumRule(GenreFilterEnum::class)]
|
||||
];
|
||||
}
|
||||
}
|
29
app/Dto/MagazineSearchCommand.php
Normal file
29
app/Dto/MagazineSearchCommand.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Dto;
|
||||
|
||||
use App\Casts\EnumCast;
|
||||
use App\Contracts\DataRequest;
|
||||
use App\Enums\MagazineOrderByEnum;
|
||||
use App\Http\Resources\V4\MagazineCollection;
|
||||
use Illuminate\Support\Optional;
|
||||
use Spatie\Enum\Laravel\Rules\EnumRule;
|
||||
use Spatie\LaravelData\Attributes\MapInputName;
|
||||
use Spatie\LaravelData\Attributes\MapOutputName;
|
||||
use Spatie\LaravelData\Attributes\WithCast;
|
||||
|
||||
/**
|
||||
* @implements DataRequest<MagazineCollection>
|
||||
*/
|
||||
final class MagazineSearchCommand extends SearchCommand implements DataRequest
|
||||
{
|
||||
#[MapInputName("order_by"), MapOutputName("order_by"), WithCast(EnumCast::class, MagazineOrderByEnum::class)]
|
||||
public MagazineOrderByEnum|Optional $orderBy;
|
||||
|
||||
public static function rules(): array
|
||||
{
|
||||
return [
|
||||
"order_by" => [new EnumRule(MagazineOrderByEnum::class)]
|
||||
];
|
||||
}
|
||||
}
|
13
app/Dto/MangaGenreListCommand.php
Normal file
13
app/Dto/MangaGenreListCommand.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Dto;
|
||||
|
||||
use App\Contracts\DataRequest;
|
||||
use App\Http\Resources\V4\GenreCollection;
|
||||
|
||||
/**
|
||||
* @implements DataRequest<GenreCollection>
|
||||
*/
|
||||
final class MangaGenreListCommand extends GenreListCommand implements DataRequest
|
||||
{
|
||||
}
|
39
app/Dto/MangaSearchCommand.php
Normal file
39
app/Dto/MangaSearchCommand.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Dto;
|
||||
|
||||
use App\Casts\EnumCast;
|
||||
use App\Contracts\DataRequest;
|
||||
use App\Enums\MangaOrderByEnum;
|
||||
use App\Enums\MangaStatusEnum;
|
||||
use App\Http\Resources\V4\MangaCollection;
|
||||
use Spatie\Enum\Laravel\Rules\EnumRule;
|
||||
use Spatie\LaravelData\Attributes\MapInputName;
|
||||
use Spatie\LaravelData\Attributes\MapOutputName;
|
||||
use Spatie\LaravelData\Attributes\Validation\StringType;
|
||||
use Spatie\LaravelData\Attributes\WithCast;
|
||||
use Spatie\LaravelData\Optional;
|
||||
|
||||
/**
|
||||
* @implements DataRequest<MangaCollection>
|
||||
*/
|
||||
final class MangaSearchCommand extends MediaSearchCommand implements DataRequest
|
||||
{
|
||||
#[WithCast(EnumCast::class, MangaStatusEnum::class)]
|
||||
public MangaStatusEnum|Optional $status;
|
||||
|
||||
#[StringType]
|
||||
public string|Optional $magazines;
|
||||
|
||||
#[MapInputName("order_by"), MapOutputName("order_by"), WithCast(EnumCast::class, MangaOrderByEnum::class)]
|
||||
public MangaOrderByEnum|Optional $orderBy;
|
||||
|
||||
public static function rules(): array
|
||||
{
|
||||
return [
|
||||
...parent::rules(),
|
||||
"status" => new EnumRule(MangaStatusEnum::class),
|
||||
"order_by" => new EnumRule(MangaOrderByEnum::class)
|
||||
];
|
||||
}
|
||||
}
|
47
app/Dto/MediaSearchCommand.php
Normal file
47
app/Dto/MediaSearchCommand.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace App\Dto;
|
||||
|
||||
use Carbon\CarbonImmutable;
|
||||
use Spatie\LaravelData\Attributes\MapInputName;
|
||||
use Spatie\LaravelData\Attributes\MapOutputName;
|
||||
use Spatie\LaravelData\Attributes\Validation\AfterOrEqual;
|
||||
use Spatie\LaravelData\Attributes\Validation\BeforeOrEqual;
|
||||
use Spatie\LaravelData\Attributes\Validation\Between;
|
||||
use Spatie\LaravelData\Attributes\Validation\DateFormat;
|
||||
use Spatie\LaravelData\Attributes\Validation\GreaterThanOrEqualTo;
|
||||
use Spatie\LaravelData\Attributes\Validation\LessThanOrEqualTo;
|
||||
use Spatie\LaravelData\Attributes\Validation\Numeric;
|
||||
use Spatie\LaravelData\Attributes\Validation\Prohibits;
|
||||
use Spatie\LaravelData\Attributes\WithCast;
|
||||
use Spatie\LaravelData\Attributes\WithTransformer;
|
||||
use Spatie\LaravelData\Casts\DateTimeInterfaceCast;
|
||||
use Spatie\LaravelData\Optional;
|
||||
use Spatie\LaravelData\Transformers\DateTimeInterfaceTransformer;
|
||||
|
||||
class MediaSearchCommand extends SearchCommand
|
||||
{
|
||||
#[MapInputName("min_score"), MapOutputName("min_score"), Between(1.00, 9.99), Numeric]
|
||||
public float|Optional $minScore;
|
||||
|
||||
#[MapInputName("max_score"), MapOutputName("max_score"), Between(1.00, 9.99), Numeric]
|
||||
public float|Optional $maxScore;
|
||||
|
||||
#[Between(1.00, 9.99), Numeric, Prohibits(["min_score", "max_score"])]
|
||||
public float|Optional $score;
|
||||
|
||||
public bool|Optional $sfw;
|
||||
|
||||
public string|Optional $genres;
|
||||
|
||||
#[MapInputName("genres_exclude"), MapOutputName("genres_exclude")]
|
||||
public string|Optional $genresExclude;
|
||||
|
||||
#[WithCast(DateTimeInterfaceCast::class), WithTransformer(DateTimeInterfaceTransformer::class)]
|
||||
#[BeforeOrEqual("end_date"), DateFormat("Y-m-d")]
|
||||
public CarbonImmutable|Optional $start_date;
|
||||
|
||||
#[WithCast(DateTimeInterfaceCast::class), WithTransformer(DateTimeInterfaceTransformer::class)]
|
||||
#[AfterOrEqual("start_date"), DateFormat("Y-m-d")]
|
||||
public CarbonImmutable|Optional $end_date;
|
||||
}
|
29
app/Dto/PeopleSearchCommand.php
Normal file
29
app/Dto/PeopleSearchCommand.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Dto;
|
||||
|
||||
use App\Casts\EnumCast;
|
||||
use App\Contracts\DataRequest;
|
||||
use App\Enums\PeopleOrderByEnum;
|
||||
use App\Http\Resources\V4\PersonCollection;
|
||||
use Illuminate\Support\Optional;
|
||||
use Spatie\Enum\Laravel\Rules\EnumRule;
|
||||
use Spatie\LaravelData\Attributes\MapInputName;
|
||||
use Spatie\LaravelData\Attributes\MapOutputName;
|
||||
use Spatie\LaravelData\Attributes\WithCast;
|
||||
|
||||
/**
|
||||
* @implements DataRequest<PersonCollection>
|
||||
*/
|
||||
final class PeopleSearchCommand extends SearchCommand implements DataRequest
|
||||
{
|
||||
#[MapInputName("order_by"), MapOutputName("order_by"), WithCast(EnumCast::class, PeopleOrderByEnum::class)]
|
||||
public PeopleOrderByEnum|Optional $orderBy;
|
||||
|
||||
public static function rules(): array
|
||||
{
|
||||
return [
|
||||
"order_by" => [new EnumRule(PeopleOrderByEnum::class)]
|
||||
];
|
||||
}
|
||||
}
|
29
app/Dto/ProducersSearchCommand.php
Normal file
29
app/Dto/ProducersSearchCommand.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Dto;
|
||||
|
||||
use App\Casts\EnumCast;
|
||||
use App\Contracts\DataRequest;
|
||||
use App\Enums\ProducerOrderByEnum;
|
||||
use App\Http\Resources\V4\ProducerCollection;
|
||||
use Illuminate\Support\Optional;
|
||||
use Spatie\Enum\Laravel\Rules\EnumRule;
|
||||
use Spatie\LaravelData\Attributes\MapInputName;
|
||||
use Spatie\LaravelData\Attributes\MapOutputName;
|
||||
use Spatie\LaravelData\Attributes\WithCast;
|
||||
|
||||
/**
|
||||
* @implements DataRequest<ProducerCollection>
|
||||
*/
|
||||
final class ProducersSearchCommand extends SearchCommand implements DataRequest
|
||||
{
|
||||
#[MapInputName("order_by"), MapOutputName("order_by"), WithCast(EnumCast::class, ProducerOrderByEnum::class)]
|
||||
public ProducerOrderByEnum|Optional $orderBy;
|
||||
|
||||
public static function rules(): array
|
||||
{
|
||||
return [
|
||||
"order_by" => [new EnumRule(ProducerOrderByEnum::class)]
|
||||
];
|
||||
}
|
||||
}
|
21
app/Dto/QueryFullAnimeCommand.php
Normal file
21
app/Dto/QueryFullAnimeCommand.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?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;
|
||||
}
|
32
app/Dto/QueryTopAnimeItemsCommand.php
Normal file
32
app/Dto/QueryTopAnimeItemsCommand.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Dto;
|
||||
|
||||
use App\Casts\EnumCast;
|
||||
use App\Contracts\DataRequest;
|
||||
use App\Enums\AnimeTypeEnum;
|
||||
use App\Enums\TopAnimeFilterEnum;
|
||||
use App\Http\Resources\V4\AnimeCollection;
|
||||
use Spatie\Enum\Laravel\Rules\EnumRule;
|
||||
use Spatie\LaravelData\Attributes\WithCast;
|
||||
use Spatie\LaravelData\Optional;
|
||||
|
||||
/**
|
||||
* @implements DataRequest<AnimeCollection>
|
||||
*/
|
||||
final class QueryTopAnimeItemsCommand extends QueryTopItemsCommand implements DataRequest
|
||||
{
|
||||
#[WithCast(EnumCast::class, AnimeTypeEnum::class)]
|
||||
public AnimeTypeEnum|Optional $type;
|
||||
|
||||
#[WithCast(EnumCast::class, TopAnimeFilterEnum::class)]
|
||||
public TopAnimeFilterEnum|Optional $filter;
|
||||
|
||||
public static function rules(): array
|
||||
{
|
||||
return [
|
||||
"type" => [new EnumRule(AnimeTypeEnum::class)],
|
||||
"filter" => [new EnumRule(TopAnimeFilterEnum::class)]
|
||||
];
|
||||
}
|
||||
}
|
13
app/Dto/QueryTopCharactersCommand.php
Normal file
13
app/Dto/QueryTopCharactersCommand.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Dto;
|
||||
|
||||
use App\Contracts\DataRequest;
|
||||
use App\Http\Resources\V4\CharacterCollection;
|
||||
|
||||
/**
|
||||
* @implements DataRequest<CharacterCollection>
|
||||
*/
|
||||
final class QueryTopCharactersCommand extends QueryTopItemsCommand implements DataRequest
|
||||
{
|
||||
}
|
13
app/Dto/QueryTopItemsCommand.php
Normal file
13
app/Dto/QueryTopItemsCommand.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Dto;
|
||||
|
||||
use Illuminate\Support\Optional;
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
abstract class QueryTopItemsCommand extends Data
|
||||
{
|
||||
public int|Optional $page;
|
||||
|
||||
public int|Optional $limit;
|
||||
}
|
33
app/Dto/QueryTopMangaItemsCommand.php
Normal file
33
app/Dto/QueryTopMangaItemsCommand.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Dto;
|
||||
|
||||
use App\Casts\EnumCast;
|
||||
use App\Contracts\DataRequest;
|
||||
use App\Enums\MangaTypeEnum;
|
||||
use App\Enums\TopMangaFilterEnum;
|
||||
use App\Http\Resources\V4\MangaCollection;
|
||||
use Illuminate\Support\Optional;
|
||||
use Spatie\Enum\Laravel\Rules\EnumRule;
|
||||
use Spatie\LaravelData\Attributes\Validation\Rule;
|
||||
use Spatie\LaravelData\Attributes\WithCast;
|
||||
|
||||
/**
|
||||
* @implements DataRequest<MangaCollection>
|
||||
*/
|
||||
final class QueryTopMangaItemsCommand extends QueryTopItemsCommand implements DataRequest
|
||||
{
|
||||
#[WithCast(EnumCast::class, MangaTypeEnum::class)]
|
||||
public MangaTypeEnum|Optional $type;
|
||||
|
||||
#[WithCast(EnumCast::class, TopMangaFilterEnum::class)]
|
||||
public TopMangaFilterEnum|Optional $filter;
|
||||
|
||||
public static function rules(): array
|
||||
{
|
||||
return [
|
||||
"type" => [new EnumRule(MangaTypeEnum::class)],
|
||||
"filter" => [new EnumRule(TopMangaFilterEnum::class)]
|
||||
];
|
||||
}
|
||||
}
|
13
app/Dto/QueryTopPeopleCommand.php
Normal file
13
app/Dto/QueryTopPeopleCommand.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Dto;
|
||||
|
||||
use App\Contracts\DataRequest;
|
||||
use App\Http\Resources\V4\PersonCollection;
|
||||
|
||||
/**
|
||||
* @implements DataRequest<PersonCollection>
|
||||
*/
|
||||
final class QueryTopPeopleCommand extends QueryTopItemsCommand implements DataRequest
|
||||
{
|
||||
}
|
15
app/Dto/QueryTopReviewsCommand.php
Normal file
15
app/Dto/QueryTopReviewsCommand.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Dto;
|
||||
|
||||
use App\Concerns\HasRequestFingerprint;
|
||||
use App\Contracts\DataRequest;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
/**
|
||||
* @implements DataRequest<JsonResponse>
|
||||
*/
|
||||
final class QueryTopReviewsCommand extends QueryTopItemsCommand implements DataRequest
|
||||
{
|
||||
use HasRequestFingerprint;
|
||||
}
|
44
app/Dto/SearchCommand.php
Normal file
44
app/Dto/SearchCommand.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Dto;
|
||||
|
||||
use App\Casts\EnumCast;
|
||||
use App\Enums\SortDirection;
|
||||
use Spatie\Enum\Laravel\Rules\EnumRule;
|
||||
use Spatie\LaravelData\Attributes\MapInputName;
|
||||
use Spatie\LaravelData\Attributes\MapOutputName;
|
||||
use Spatie\LaravelData\Attributes\Validation\Alpha;
|
||||
use Spatie\LaravelData\Attributes\Validation\IntegerType;
|
||||
use Spatie\LaravelData\Attributes\Validation\Max;
|
||||
use Spatie\LaravelData\Attributes\Validation\Min;
|
||||
use Spatie\LaravelData\Attributes\Validation\Size;
|
||||
use Spatie\LaravelData\Attributes\Validation\StringType;
|
||||
use Spatie\LaravelData\Attributes\WithCast;
|
||||
use Spatie\LaravelData\Data;
|
||||
use Spatie\LaravelData\Optional;
|
||||
|
||||
class SearchCommand extends Data
|
||||
{
|
||||
/**
|
||||
* The search keywords
|
||||
* @var string|Optional
|
||||
*/
|
||||
#[Max(255), StringType]
|
||||
public string|Optional $q;
|
||||
|
||||
#[IntegerType, Min(1)]
|
||||
public int|Optional $limit;
|
||||
|
||||
#[WithCast(EnumCast::class, SortDirection::class)]
|
||||
public SortDirection|Optional $sort;
|
||||
|
||||
#[Size(1), StringType, Alpha]
|
||||
public string|Optional $letter;
|
||||
|
||||
public static function rules(): array
|
||||
{
|
||||
return [
|
||||
"sort" => [new EnumRule(SortDirection::class)]
|
||||
];
|
||||
}
|
||||
}
|
39
app/Dto/UsersSearchCommand.php
Normal file
39
app/Dto/UsersSearchCommand.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Dto;
|
||||
|
||||
use App\Casts\EnumCast;
|
||||
use App\Concerns\HasRequestFingerprint;
|
||||
use App\Contracts\DataRequest;
|
||||
use App\Enums\GenderEnum;
|
||||
use App\Http\Resources\V4\UserCollection;
|
||||
use Illuminate\Support\Optional;
|
||||
use Spatie\Enum\Laravel\Rules\EnumRule;
|
||||
use Spatie\LaravelData\Attributes\Validation\StringType;
|
||||
use Spatie\LaravelData\Attributes\WithCast;
|
||||
|
||||
/**
|
||||
* @implements DataRequest<UserCollection>
|
||||
*/
|
||||
final class UsersSearchCommand extends SearchCommand implements DataRequest
|
||||
{
|
||||
use HasRequestFingerprint;
|
||||
|
||||
public int|Optional $minAge;
|
||||
|
||||
public int|Optional $maxAge;
|
||||
|
||||
#[WithCast(EnumCast::class, GenderEnum::class)]
|
||||
public GenderEnum|Optional $gender;
|
||||
|
||||
#[StringType]
|
||||
public string|Optional $location;
|
||||
|
||||
public static function rules(): array
|
||||
{
|
||||
return [
|
||||
...parent::rules(),
|
||||
"gender" => [new EnumRule(GenderEnum::class)]
|
||||
];
|
||||
}
|
||||
}
|
38
app/Enums/AnimeOrderByEnum.php
Normal file
38
app/Enums/AnimeOrderByEnum.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use Spatie\Enum\Laravel\Enum;
|
||||
|
||||
/**
|
||||
* @method static self mal_id()
|
||||
* @method static self title()
|
||||
* @method static self type()
|
||||
* @method static self rating()
|
||||
* @method static self start_date()
|
||||
* @method static self end_date()
|
||||
* @method static self episodes()
|
||||
* @method static self score()
|
||||
* @method static self scored_by()
|
||||
* @method static self rank
|
||||
* @method static self popularity()
|
||||
* @method static self members()
|
||||
* @method static self favorites()
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="anime_search_query_orderby",
|
||||
* description="Available Anime order_by properties",
|
||||
* type="string",
|
||||
* enum={"mal_id", "title", "type", "rating", "start_date", "end_date", "episodes", "score", "scored_by", "rank", "popularity", "members", "favorites" }
|
||||
* )
|
||||
*/
|
||||
final class AnimeOrderByEnum extends Enum
|
||||
{
|
||||
protected static function labels(): array
|
||||
{
|
||||
return [
|
||||
'start_date' => 'aired.from',
|
||||
'end_date' => 'aired.to',
|
||||
];
|
||||
}
|
||||
}
|
35
app/Enums/AnimeRatingEnum.php
Normal file
35
app/Enums/AnimeRatingEnum.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use Spatie\Enum\Laravel\Enum;
|
||||
|
||||
/**
|
||||
* @method static self g()
|
||||
* @method static self pg()
|
||||
* @method static self pg13()
|
||||
* @method static self r17()
|
||||
* @method static self r()
|
||||
* @method static self rx()
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="anime_search_query_rating",
|
||||
* description="Available Anime audience ratings<br><br><b>Ratings</b><br><ul><li>G - All Ages</li><li>PG - Children</li><li>PG-13 - Teens 13 or older</li><li>R - 17+ (violence & profanity)</li><li>R+ - Mild Nudity</li><li>Rx - Hentai</li></ul>",
|
||||
* type="string",
|
||||
* enum={"g","pg","pg13","r17","r","rx"}
|
||||
* )
|
||||
*/
|
||||
final class AnimeRatingEnum extends Enum
|
||||
{
|
||||
protected static function labels(): array
|
||||
{
|
||||
return [
|
||||
"g" => "G - All Ages",
|
||||
"pg" => "PG - Children",
|
||||
"pg13" => "PG-13 - Teens 13 or older",
|
||||
"r17" => "R - 17+ (violence & profanity)",
|
||||
"r" => "R+ - Mild Nudity",
|
||||
"rx" => "Rx - Hentai"
|
||||
];
|
||||
}
|
||||
}
|
28
app/Enums/AnimeStatusEnum.php
Normal file
28
app/Enums/AnimeStatusEnum.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
use Spatie\Enum\Laravel\Enum;
|
||||
|
||||
/**
|
||||
* @method static self airing()
|
||||
* @method static self complete()
|
||||
* @method static self upcoming()
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="anime_search_query_status",
|
||||
* description="Available Anime statuses",
|
||||
* type="string",
|
||||
* enum={"airing","complete","upcoming"}
|
||||
* )
|
||||
*/
|
||||
final class AnimeStatusEnum extends Enum
|
||||
{
|
||||
protected static function labels(): array
|
||||
{
|
||||
return [
|
||||
"airing" => "Currently Airing",
|
||||
"complete" => "Finished Airing",
|
||||
"upcoming" => "Not yet aired",
|
||||
];
|
||||
}
|
||||
}
|
35
app/Enums/AnimeTypeEnum.php
Normal file
35
app/Enums/AnimeTypeEnum.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use Spatie\Enum\Laravel\Enum;
|
||||
|
||||
/**
|
||||
* @method static self tv()
|
||||
* @method static self movie()
|
||||
* @method static self ova()
|
||||
* @method static self special()
|
||||
* @method static self ona()
|
||||
* @method static self music()
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="anime_search_query_type",
|
||||
* description="Available Anime types",
|
||||
* type="string",
|
||||
* enum={"tv","movie","ova","special","ona","music"}
|
||||
* )
|
||||
*/
|
||||
final class AnimeTypeEnum extends Enum
|
||||
{
|
||||
protected static function labels(): array
|
||||
{
|
||||
return [
|
||||
'tv' => 'TV',
|
||||
'movie' => 'Movie',
|
||||
'ova' => 'OVA',
|
||||
'special' => 'Special',
|
||||
'ona' => 'ONA',
|
||||
'music' => 'Music'
|
||||
];
|
||||
}
|
||||
}
|
27
app/Enums/CharacterOrderByEnum.php
Normal file
27
app/Enums/CharacterOrderByEnum.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use Spatie\Enum\Laravel\Enum;
|
||||
|
||||
/**
|
||||
* @method static self mal_id()
|
||||
* @method static self name()
|
||||
* @method static self favorites()
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="characters_search_query_orderby",
|
||||
* description="Available Character order_by properties",
|
||||
* type="string",
|
||||
* enum={"mal_id", "name", "favorites"}
|
||||
* )
|
||||
*/
|
||||
final class CharacterOrderByEnum extends Enum
|
||||
{
|
||||
protected static function labels(): array
|
||||
{
|
||||
return [
|
||||
"favorites" => "member_favorites"
|
||||
];
|
||||
}
|
||||
}
|
51
app/Enums/ClubCategoryEnum.php
Normal file
51
app/Enums/ClubCategoryEnum.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use Spatie\Enum\Laravel\Enum;
|
||||
|
||||
/**
|
||||
* @method static self anime()
|
||||
* @method static self manga()
|
||||
* @method static self actors_and_artists()
|
||||
* @method static self characters()
|
||||
* @method static self cities_and_neighborhoods()
|
||||
* @method static self companies()
|
||||
* @method static self conventions()
|
||||
* @method static self games()
|
||||
* @method static self japan()
|
||||
* @method static self music()
|
||||
* @method static self other()
|
||||
* @method static self schools()
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="club_search_query_category",
|
||||
* description="Club Search Query Category",
|
||||
* type="string",
|
||||
* enum={
|
||||
* "anime","manga","actors_and_artists","characters",
|
||||
* "cities_and_neighborhoods","companies","conventions","games",
|
||||
* "japan","music","other","schools"
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
final class ClubCategoryEnum extends Enum
|
||||
{
|
||||
protected static function labels(): array
|
||||
{
|
||||
return [
|
||||
'anime' => 'Anime',
|
||||
'manga' => 'Manga',
|
||||
'actors_and_artists' => 'Actors & Artists',
|
||||
'characters' => 'Characters',
|
||||
'cities_and_neighborhoods' => 'Cities & Neighborhoods',
|
||||
'companies' => 'Companies',
|
||||
'conventions' => 'Conventions',
|
||||
'games' => 'Games',
|
||||
'japan' => 'Japan',
|
||||
'music' => 'Music',
|
||||
'other' => 'Other',
|
||||
'schools' => 'Schools'
|
||||
];
|
||||
}
|
||||
}
|
23
app/Enums/ClubOrderByEnum.php
Normal file
23
app/Enums/ClubOrderByEnum.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use Spatie\Enum\Laravel\Enum;
|
||||
|
||||
/**
|
||||
* @method static self mal_id()
|
||||
* @method static self title()
|
||||
* @method static self members_count()
|
||||
* @method static self pictures_count()
|
||||
* @method static self created()
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="club_search_query_orderby",
|
||||
* description="Club Search Query OrderBy",
|
||||
* type="string",
|
||||
* enum={"mal_id","title","members_count","pictures_count","created"}
|
||||
* )
|
||||
*/
|
||||
final class ClubOrderByEnum extends Enum
|
||||
{
|
||||
}
|
21
app/Enums/ClubTypeEnum.php
Normal file
21
app/Enums/ClubTypeEnum.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use Spatie\Enum\Laravel\Enum;
|
||||
|
||||
/**
|
||||
* @method static self public()
|
||||
* @method static self private()
|
||||
* @method static self secret()
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="club_search_query_type",
|
||||
* description="Club Search Query Type",
|
||||
* type="string",
|
||||
* enum={"public","private","secret"}
|
||||
* )
|
||||
*/
|
||||
final class ClubTypeEnum extends Enum
|
||||
{
|
||||
}
|
24
app/Enums/GenderEnum.php
Normal file
24
app/Enums/GenderEnum.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use Jikan\Helper\Constants as JikanConstants;
|
||||
|
||||
/**
|
||||
* @method static self any()
|
||||
* @method static self male()
|
||||
* @method static self female()
|
||||
* @method static self nonbinary()
|
||||
*/
|
||||
final class GenderEnum extends \Spatie\Enum\Laravel\Enum
|
||||
{
|
||||
protected static function labels(): array
|
||||
{
|
||||
return [
|
||||
'any' => JikanConstants::SEARCH_USER_GENDER_ANY,
|
||||
'male' => JikanConstants::SEARCH_USER_GENDER_MALE,
|
||||
'female' => JikanConstants::SEARCH_USER_GENDER_FEMALE,
|
||||
'nonbinary' => JikanConstants::SEARCH_USER_GENDER_NONBINARY
|
||||
];
|
||||
}
|
||||
}
|
22
app/Enums/GenreFilterEnum.php
Normal file
22
app/Enums/GenreFilterEnum.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use Spatie\Enum\Laravel\Enum;
|
||||
|
||||
/**
|
||||
* @method static self genres()
|
||||
* @method static self explicit_genres()
|
||||
* @method static self themes()
|
||||
* @method static self demographics()
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="genre_query_filter",
|
||||
* description="Filter genres by type",
|
||||
* type="string",
|
||||
* enum={"genres","explicit_genres", "themes", "demographics"}
|
||||
* )
|
||||
*/
|
||||
final class GenreFilterEnum extends Enum
|
||||
{
|
||||
}
|
21
app/Enums/MagazineOrderByEnum.php
Normal file
21
app/Enums/MagazineOrderByEnum.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use Spatie\Enum\Laravel\Enum;
|
||||
|
||||
/**
|
||||
* @method static self mal_id()
|
||||
* @method static self name()
|
||||
* @method static self count()
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="magazines_query_orderby",
|
||||
* description="Order by magazine data",
|
||||
* type="string",
|
||||
* enum={"mal_id", "name", "count"}
|
||||
* )
|
||||
*/
|
||||
final class MagazineOrderByEnum extends Enum
|
||||
{
|
||||
}
|
37
app/Enums/MangaOrderByEnum.php
Normal file
37
app/Enums/MangaOrderByEnum.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use Spatie\Enum\Laravel\Enum;
|
||||
|
||||
/**
|
||||
* @method static self mal_id()
|
||||
* @method static self title()
|
||||
* @method static self start_date()
|
||||
* @method static self end_date()
|
||||
* @method static self chapters()
|
||||
* @method static self volumes()
|
||||
* @method static self score()
|
||||
* @method static self scored_by()
|
||||
* @method static self rank()
|
||||
* @method static self popularity()
|
||||
* @method static self members()
|
||||
* @method static self favorites()
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="manga_search_query_orderby",
|
||||
* description="Available Manga order_by properties",
|
||||
* type="string",
|
||||
* enum={"mal_id", "title", "start_date", "end_date", "chapters", "volumes", "score", "scored_by", "rank", "popularity", "members", "favorites"}
|
||||
* )
|
||||
*/
|
||||
final class MangaOrderByEnum extends Enum
|
||||
{
|
||||
protected static function labels(): array
|
||||
{
|
||||
return [
|
||||
'start_date' => 'published.from',
|
||||
'end_date' => 'published.to'
|
||||
];
|
||||
}
|
||||
}
|
33
app/Enums/MangaStatusEnum.php
Normal file
33
app/Enums/MangaStatusEnum.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use Spatie\Enum\Laravel\Enum;
|
||||
|
||||
/**
|
||||
* @method static self publishing()
|
||||
* @method static self complete()
|
||||
* @method static self hiatus()
|
||||
* @method static self discontinued()
|
||||
* @method static self upcoming()
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="manga_search_query_status",
|
||||
* description="Available Manga statuses",
|
||||
* type="string",
|
||||
* enum={"publishing","complete","hiatus","discontinued","upcoming"}
|
||||
* )
|
||||
*/
|
||||
final class MangaStatusEnum extends Enum
|
||||
{
|
||||
protected static function labels(): array
|
||||
{
|
||||
return [
|
||||
"publishing" => "Publishing",
|
||||
"complete" => "Finished",
|
||||
"hiatus" => "On Hiatus",
|
||||
"discontinued" => "Discontinued",
|
||||
"upcoming" => "Not yet published"
|
||||
];
|
||||
}
|
||||
}
|
37
app/Enums/MangaTypeEnum.php
Normal file
37
app/Enums/MangaTypeEnum.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use Spatie\Enum\Laravel\Enum;
|
||||
|
||||
/**
|
||||
* @method static self manga()
|
||||
* @method static self novel()
|
||||
* @method static self lightnovel()
|
||||
* @method static self oneshot()
|
||||
* @method static self doujin()
|
||||
* @method static self manhwa()
|
||||
* @method static self manhua()
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="manga_search_query_type",
|
||||
* description="Available Manga types",
|
||||
* type="string",
|
||||
* enum={"manga","novel", "lightnovel", "oneshot","doujin","manhwa","manhua"}
|
||||
* )
|
||||
*/
|
||||
final class MangaTypeEnum extends Enum
|
||||
{
|
||||
protected static function labels(): array
|
||||
{
|
||||
return [
|
||||
'manga' => 'Manga',
|
||||
'novel' => 'Novel',
|
||||
'lightnovel' => 'Light Novel',
|
||||
'oneshot' => 'One-shot',
|
||||
'doujin' => 'Doujinshi',
|
||||
'manhwa' => 'Manhwa',
|
||||
'manhua' => 'Manhua'
|
||||
];
|
||||
}
|
||||
}
|
28
app/Enums/PeopleOrderByEnum.php
Normal file
28
app/Enums/PeopleOrderByEnum.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use Spatie\Enum\Laravel\Enum;
|
||||
|
||||
/**
|
||||
* @method static self mal_id()
|
||||
* @method static self name()
|
||||
* @method static self birthday()
|
||||
* @method static self favorites()
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="people_search_query_orderby",
|
||||
* description="Available People order_by properties",
|
||||
* type="string",
|
||||
* enum={"mal_id", "name", "birthday", "favorites"}
|
||||
* )
|
||||
*/
|
||||
final class PeopleOrderByEnum extends Enum
|
||||
{
|
||||
protected static function labels()
|
||||
{
|
||||
return [
|
||||
"favorites" => "member_favorites"
|
||||
];
|
||||
}
|
||||
}
|
22
app/Enums/ProducerOrderByEnum.php
Normal file
22
app/Enums/ProducerOrderByEnum.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use Spatie\Enum\Laravel\Enum;
|
||||
|
||||
/**
|
||||
* @method static self mal_id()
|
||||
* @method static self count()
|
||||
* @method static self favorites()
|
||||
* @method static self established()
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="producers_query_orderby",
|
||||
* description="Producers Search Query Order By",
|
||||
* type="string",
|
||||
* enum={"mal_id", "count", "favorites", "established"}
|
||||
* )
|
||||
*/
|
||||
final class ProducerOrderByEnum extends Enum
|
||||
{
|
||||
}
|
19
app/Enums/SortDirection.php
Normal file
19
app/Enums/SortDirection.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
use Spatie\Enum\Laravel\Enum;
|
||||
|
||||
/**
|
||||
* @method static self asc()
|
||||
* @method static self desc()
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="search_query_sort",
|
||||
* description="Search query sort direction",
|
||||
* type="string",
|
||||
* enum={"desc","asc"}
|
||||
* )
|
||||
*/
|
||||
final class SortDirection extends Enum
|
||||
{
|
||||
}
|
22
app/Enums/TopAnimeFilterEnum.php
Normal file
22
app/Enums/TopAnimeFilterEnum.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use Spatie\Enum\Laravel\Enum;
|
||||
|
||||
/**
|
||||
* @method static self airing()
|
||||
* @method static self upcoming()
|
||||
* @method static self bypopularity()
|
||||
* @method static self favorite()
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="top_anime_filter",
|
||||
* description="Top items filter types",
|
||||
* type="string",
|
||||
* enum={"airing","upcoming","bypopularity","favorite"}
|
||||
* )
|
||||
*/
|
||||
final class TopAnimeFilterEnum extends Enum
|
||||
{
|
||||
}
|
22
app/Enums/TopMangaFilterEnum.php
Normal file
22
app/Enums/TopMangaFilterEnum.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use Spatie\Enum\Laravel\Enum;
|
||||
|
||||
/**
|
||||
* @method static self publishing()
|
||||
* @method static self upcoming()
|
||||
* @method static self bypopularity()
|
||||
* @method static self favorite()
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="top_manga_filter",
|
||||
* description="Top items filter types",
|
||||
* type="string",
|
||||
* enum={"publishing","upcoming","bypopularity","favorite"}
|
||||
* )
|
||||
*/
|
||||
final class TopMangaFilterEnum extends Enum
|
||||
{
|
||||
}
|
19
app/Features/AnimeGenreListHandler.php
Normal file
19
app/Features/AnimeGenreListHandler.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Features;
|
||||
|
||||
use App\Dto\AnimeGenreListCommand;
|
||||
|
||||
/**
|
||||
* @implements GenreListHandler<AnimeGenreListCommand>
|
||||
*/
|
||||
final class AnimeGenreListHandler extends GenreListHandler
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function requestClass(): string
|
||||
{
|
||||
return AnimeGenreListCommand::class;
|
||||
}
|
||||
}
|
26
app/Features/AnimeSearchHandler.php
Normal file
26
app/Features/AnimeSearchHandler.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Features;
|
||||
|
||||
use App\Dto\AnimeSearchCommand;
|
||||
use App\Http\Resources\V4\AnimeCollection;
|
||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||
|
||||
/**
|
||||
* @extends SearchRequestHandler<AnimeSearchCommand, AnimeCollection>
|
||||
*/
|
||||
class AnimeSearchHandler extends SearchRequestHandler
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function requestClass(): string
|
||||
{
|
||||
return AnimeSearchCommand::class;
|
||||
}
|
||||
|
||||
protected function renderResponse(LengthAwarePaginator $paginator): AnimeCollection
|
||||
{
|
||||
return new AnimeCollection($paginator);
|
||||
}
|
||||
}
|
29
app/Features/CharacterSearchHandler.php
Normal file
29
app/Features/CharacterSearchHandler.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Features;
|
||||
|
||||
use App\Dto\CharactersSearchCommand;
|
||||
use App\Http\Resources\V4\CharacterCollection;
|
||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||
|
||||
/**
|
||||
* @extends SearchRequestHandler<CharactersSearchCommand, CharacterCollection>
|
||||
*/
|
||||
class CharacterSearchHandler extends SearchRequestHandler
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function requestClass(): string
|
||||
{
|
||||
return CharactersSearchCommand::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected function renderResponse(LengthAwarePaginator $paginator)
|
||||
{
|
||||
return new CharacterCollection($paginator);
|
||||
}
|
||||
}
|
30
app/Features/ClubSearchHandler.php
Normal file
30
app/Features/ClubSearchHandler.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Features;
|
||||
|
||||
use App\Dto\ClubSearchCommand;
|
||||
use App\Http\Resources\V4\ClubCollection;
|
||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||
|
||||
/**
|
||||
* @extends SearchRequestHandler<ClubSearchCommand, ClubCollection>
|
||||
*/
|
||||
class ClubSearchHandler extends SearchRequestHandler
|
||||
{
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function requestClass(): string
|
||||
{
|
||||
return ClubSearchCommand::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected function renderResponse(LengthAwarePaginator $paginator)
|
||||
{
|
||||
return new ClubCollection($paginator);
|
||||
}
|
||||
}
|
43
app/Features/GenreListHandler.php
Normal file
43
app/Features/GenreListHandler.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Features;
|
||||
|
||||
use App\Contracts\GenreRepository;
|
||||
use App\Contracts\RequestHandler;
|
||||
use App\Dto\GenreListCommand;
|
||||
use App\Enums\GenreFilterEnum;
|
||||
use App\Http\Resources\V4\GenreCollection;
|
||||
|
||||
/**
|
||||
* @template TRequest of GenreListCommand
|
||||
* @implements RequestHandler<TRequest, GenreCollection>
|
||||
*/
|
||||
abstract class GenreListHandler implements RequestHandler
|
||||
{
|
||||
public function __construct(private readonly GenreRepository $repository)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param GenreListCommand $request
|
||||
* @returns GenreCollection
|
||||
*/
|
||||
public function handle($request): GenreCollection
|
||||
{
|
||||
$requestParams = collect($request->all());
|
||||
/**
|
||||
* @var ?GenreFilterEnum $filterParam
|
||||
*/
|
||||
$filterParam = $requestParams->has("filter") ? $request->filter : null;
|
||||
|
||||
$results = match($filterParam) {
|
||||
GenreFilterEnum::genres() => $this->repository->genres(),
|
||||
GenreFilterEnum::explicit_genres() => $this->repository->getExplicitItems(),
|
||||
GenreFilterEnum::themes() => $this->repository->getThemes(),
|
||||
GenreFilterEnum::demographics() => $this->repository->getDemographics(),
|
||||
default => $this->repository->all()
|
||||
};
|
||||
|
||||
return new GenreCollection($results);
|
||||
}
|
||||
}
|
48
app/Features/ItemLookupHandler.php
Normal file
48
app/Features/ItemLookupHandler.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Features;
|
||||
|
||||
use App\Concerns\ScraperResultCache;
|
||||
use App\Contracts\DataRequest;
|
||||
use App\Contracts\Repository;
|
||||
use App\Contracts\RequestHandler;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Collection;
|
||||
use Spatie\LaravelData\Data;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
/**
|
||||
* @template TRequest of DataRequest<TResponse>
|
||||
* @template TResponse of ResourceCollection|JsonResource|Response
|
||||
* @implements RequestHandler<TRequest, TResponse>
|
||||
*/
|
||||
abstract class ItemLookupHandler extends Data implements RequestHandler
|
||||
{
|
||||
use ScraperResultCache;
|
||||
|
||||
public function __construct(protected readonly Repository $repository)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TRequest $request
|
||||
* @return TResponse
|
||||
* @throws NotFoundHttpException
|
||||
*/
|
||||
public function handle($request)
|
||||
{
|
||||
$requestFingerprint = $request->getFingerPrint();
|
||||
$results = $this->queryFromScraperCacheById(
|
||||
$this->repository,
|
||||
$request->id,
|
||||
$request->getFingerPrint(),
|
||||
);
|
||||
|
||||
$resource = $this->resource($results);
|
||||
return $this->prepareResponse($requestFingerprint, $results, $resource->response());
|
||||
}
|
||||
|
||||
protected abstract function resource(Collection $results): JsonResource;
|
||||
}
|
29
app/Features/MagazineSearchHandler.php
Normal file
29
app/Features/MagazineSearchHandler.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Features;
|
||||
|
||||
use App\Dto\MagazineSearchCommand;
|
||||
use App\Http\Resources\V4\MagazineCollection;
|
||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||
|
||||
/**
|
||||
* @extends SearchRequestHandler<MagazineSearchCommand, MagazineCollection>
|
||||
*/
|
||||
class MagazineSearchHandler extends SearchRequestHandler
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function requestClass(): string
|
||||
{
|
||||
return MagazineSearchCommand::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected function renderResponse(LengthAwarePaginator $paginator)
|
||||
{
|
||||
return new MagazineCollection($paginator);
|
||||
}
|
||||
}
|
19
app/Features/MangaGenreListHandler.php
Normal file
19
app/Features/MangaGenreListHandler.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Features;
|
||||
|
||||
use App\Dto\MangaGenreListCommand;
|
||||
|
||||
/**
|
||||
* @implements GenreListHandler<MangaGenreListCommand>
|
||||
*/
|
||||
final class MangaGenreListHandler extends GenreListHandler
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function requestClass(): string
|
||||
{
|
||||
return MangaGenreListCommand::class;
|
||||
}
|
||||
}
|
29
app/Features/MangaSearchHandler.php
Normal file
29
app/Features/MangaSearchHandler.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Features;
|
||||
|
||||
use App\Dto\MangaSearchCommand;
|
||||
use App\Http\Resources\V4\MangaCollection;
|
||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||
|
||||
/**
|
||||
* @extends SearchRequestHandler<MangaSearchCommand, MangaCollection>
|
||||
*/
|
||||
class MangaSearchHandler extends SearchRequestHandler
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function requestClass(): string
|
||||
{
|
||||
return MangaSearchCommand::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected function renderResponse(LengthAwarePaginator $paginator)
|
||||
{
|
||||
return new MangaCollection($paginator);
|
||||
}
|
||||
}
|
29
app/Features/PeopleSearchHandler.php
Normal file
29
app/Features/PeopleSearchHandler.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Features;
|
||||
|
||||
use App\Dto\PeopleSearchCommand;
|
||||
use App\Http\Resources\V4\PersonCollection;
|
||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||
|
||||
/**
|
||||
* @extends SearchRequestHandler<PeopleSearchCommand, PersonCollection>
|
||||
*/
|
||||
class PeopleSearchHandler extends SearchRequestHandler
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function requestClass(): string
|
||||
{
|
||||
return PeopleSearchCommand::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected function renderResponse(LengthAwarePaginator $paginator)
|
||||
{
|
||||
return new PersonCollection($paginator);
|
||||
}
|
||||
}
|
29
app/Features/ProducerSearchHandler.php
Normal file
29
app/Features/ProducerSearchHandler.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Features;
|
||||
|
||||
use App\Dto\ProducersSearchCommand;
|
||||
use App\Http\Resources\V4\ProducerCollection;
|
||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||
|
||||
/**
|
||||
* @extends SearchRequestHandler<ProducersSearchCommand, ProducerCollection>
|
||||
*/
|
||||
class ProducerSearchHandler extends SearchRequestHandler
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function requestClass(): string
|
||||
{
|
||||
return ProducersSearchCommand::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected function renderResponse(LengthAwarePaginator $paginator)
|
||||
{
|
||||
return new ProducerCollection($paginator);
|
||||
}
|
||||
}
|
33
app/Features/QueryFullAnimeHandler.php
Normal file
33
app/Features/QueryFullAnimeHandler.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Features;
|
||||
|
||||
use App\Concerns\ScraperResultCache;
|
||||
use App\Contracts\AnimeRepository;
|
||||
use App\Dto\QueryFullAnimeCommand;
|
||||
use App\Http\Resources\V4\AnimeFullResource;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
|
||||
/**
|
||||
* @extends ItemLookupHandler<QueryFullAnimeCommand, JsonResponse>
|
||||
*/
|
||||
final class QueryFullAnimeHandler extends ItemLookupHandler
|
||||
{
|
||||
public function __construct(AnimeRepository $repository)
|
||||
{
|
||||
parent::__construct($repository);
|
||||
}
|
||||
|
||||
public function requestClass(): string
|
||||
{
|
||||
return QueryFullAnimeCommand::class;
|
||||
}
|
||||
|
||||
protected function resource(Collection $results): JsonResource
|
||||
{
|
||||
return new AnimeFullResource($results->first());
|
||||
}
|
||||
}
|
55
app/Features/QueryTopAnimeItemsHandler.php
Normal file
55
app/Features/QueryTopAnimeItemsHandler.php
Normal file
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\Features;
|
||||
|
||||
use App\Contracts\AnimeRepository;
|
||||
use App\Contracts\RequestHandler;
|
||||
use App\Dto\QueryTopAnimeItemsCommand;
|
||||
use App\Enums\TopAnimeFilterEnum;
|
||||
use App\Http\Resources\V4\AnimeCollection;
|
||||
use App\Services\QueryBuilderPaginatorService;
|
||||
use Spatie\LaravelData\Optional;
|
||||
|
||||
/**
|
||||
* @implements RequestHandler<QueryTopAnimeItemsCommand, AnimeCollection>
|
||||
*/
|
||||
final class QueryTopAnimeItemsHandler implements RequestHandler
|
||||
{
|
||||
public function __construct(private readonly AnimeRepository $repository,
|
||||
private readonly QueryBuilderPaginatorService $paginatorService)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param QueryTopAnimeItemsCommand $request
|
||||
* @returns AnimeCollection
|
||||
*/
|
||||
public function handle($request): AnimeCollection
|
||||
{
|
||||
$requestParams = collect($request->all());
|
||||
/**
|
||||
* @var ?TopAnimeFilterEnum $filterType
|
||||
*/
|
||||
$filterType = $requestParams->has("filter") ? $request->filter : null;
|
||||
$builder = match($filterType) {
|
||||
TopAnimeFilterEnum::airing() => $this->repository->getTopAiringItems(),
|
||||
TopAnimeFilterEnum::upcoming() => $this->repository->getTopUpcomingItems(),
|
||||
TopAnimeFilterEnum::bypopularity() => $this->repository->orderByPopularity(),
|
||||
TopAnimeFilterEnum::favorite() => $this->repository->orderByFavoriteCount(),
|
||||
default => $this->repository->orderByRank()
|
||||
};
|
||||
|
||||
$builder = $builder->filter($requestParams);
|
||||
|
||||
return new AnimeCollection(
|
||||
$this->paginatorService->paginate(
|
||||
$builder, $requestParams->get("limit"), $requestParams->get("page")
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function requestClass(): string
|
||||
{
|
||||
return QueryTopAnimeItemsCommand::class;
|
||||
}
|
||||
}
|
43
app/Features/QueryTopCharactersHandler.php
Normal file
43
app/Features/QueryTopCharactersHandler.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Features;
|
||||
|
||||
use App\Contracts\CharacterRepository;
|
||||
use App\Contracts\RequestHandler;
|
||||
use App\Dto\QueryTopCharactersCommand;
|
||||
use App\Http\Resources\V4\CharacterCollection;
|
||||
use App\Services\QueryBuilderPaginatorService;
|
||||
|
||||
/**
|
||||
* @implements RequestHandler<QueryTopCharactersCommand, CharacterCollection>
|
||||
*/
|
||||
final class QueryTopCharactersHandler implements RequestHandler
|
||||
{
|
||||
public function __construct(
|
||||
private readonly CharacterRepository $repository,
|
||||
private readonly QueryBuilderPaginatorService $paginatorService
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param QueryTopCharactersCommand $request
|
||||
* @return CharacterCollection
|
||||
*/
|
||||
public function handle($request): CharacterCollection
|
||||
{
|
||||
$requestParams = collect($request->all());
|
||||
$topItemsQuery = $this->repository->topCharacters()->filter($requestParams);
|
||||
|
||||
$results = $this->paginatorService->paginate(
|
||||
$topItemsQuery, $requestParams->get("limit"), $requestParams->get("page")
|
||||
);
|
||||
|
||||
return new CharacterCollection($results);
|
||||
}
|
||||
|
||||
public function requestClass(): string
|
||||
{
|
||||
return QueryTopCharactersCommand::class;
|
||||
}
|
||||
}
|
57
app/Features/QueryTopMangaItemsHandler.php
Normal file
57
app/Features/QueryTopMangaItemsHandler.php
Normal file
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace App\Features;
|
||||
|
||||
use App\Contracts\MangaRepository;
|
||||
use App\Contracts\RequestHandler;
|
||||
use App\Dto\QueryTopMangaItemsCommand;
|
||||
use App\Enums\TopMangaFilterEnum;
|
||||
use App\Http\Resources\V4\MangaCollection;
|
||||
use App\Services\QueryBuilderPaginatorService;
|
||||
|
||||
/**
|
||||
* @implements RequestHandler<QueryTopMangaItemsCommand, MangaCollection>
|
||||
*/
|
||||
class QueryTopMangaItemsHandler implements RequestHandler
|
||||
{
|
||||
public function __construct(private readonly MangaRepository $repository,
|
||||
private readonly QueryBuilderPaginatorService $paginatorService)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param QueryTopMangaItemsCommand $request
|
||||
* @returns MangaCollection
|
||||
*/
|
||||
public function handle($request): MangaCollection
|
||||
{
|
||||
$requestParams = collect($request->all());
|
||||
/**
|
||||
* @var ?TopMangaFilterEnum $filterType
|
||||
*/
|
||||
$filterType = $requestParams->has("filter") ? $request->filter : null;
|
||||
$builder = match($filterType) {
|
||||
TopMangaFilterEnum::publishing() => $this->repository->getTopPublishingItems(),
|
||||
TopMangaFilterEnum::upcoming() => $this->repository->getTopUpcomingItems(),
|
||||
TopMangaFilterEnum::bypopularity() => $this->repository->orderByPopularity(),
|
||||
TopMangaFilterEnum::favorite() => $this->repository->orderByFavoriteCount(),
|
||||
default => $this->repository->orderByRank()
|
||||
};
|
||||
|
||||
$builder = $builder->filter($requestParams);
|
||||
|
||||
return new MangaCollection(
|
||||
$this->paginatorService->paginate(
|
||||
$builder, $requestParams->get("limit"), $requestParams->get("page")
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function requestClass(): string
|
||||
{
|
||||
return QueryTopMangaItemsCommand::class;
|
||||
}
|
||||
}
|
46
app/Features/QueryTopPeopleHandler.php
Normal file
46
app/Features/QueryTopPeopleHandler.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace App\Features;
|
||||
|
||||
use App\Contracts\PeopleRepository;
|
||||
use App\Contracts\RequestHandler;
|
||||
use App\Dto\QueryTopPeopleCommand;
|
||||
use App\Http\Resources\V4\PersonCollection;
|
||||
use App\Services\QueryBuilderPaginatorService;
|
||||
|
||||
/**
|
||||
* @implements RequestHandler<QueryTopPeopleCommand, PersonCollection>
|
||||
*/
|
||||
class QueryTopPeopleHandler implements RequestHandler
|
||||
{
|
||||
public function __construct(
|
||||
private readonly PeopleRepository $repository,
|
||||
private readonly QueryBuilderPaginatorService $paginatorService
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param QueryTopPeopleCommand $request
|
||||
* @returns PersonCollection
|
||||
*/
|
||||
public function handle($request)
|
||||
{
|
||||
$requestParams = collect($request->all());
|
||||
$topItemsQuery = $this->repository->topPeople()->filter($requestParams);
|
||||
|
||||
$results = $this->paginatorService->paginate(
|
||||
$topItemsQuery, $requestParams->get("limit"), $requestParams->get("page")
|
||||
);
|
||||
|
||||
return new PersonCollection($results);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function requestClass(): string
|
||||
{
|
||||
return QueryTopPeopleCommand::class;
|
||||
}
|
||||
}
|
48
app/Features/QueryTopReviewsHandler.php
Normal file
48
app/Features/QueryTopReviewsHandler.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Features;
|
||||
|
||||
use App\Concerns\ScraperResultCache;
|
||||
use App\Contracts\RequestHandler;
|
||||
use App\Dto\QueryTopReviewsCommand;
|
||||
use App\Http\Resources\V4\ResultsResource;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Jikan\Helper\Constants;
|
||||
use Jikan\MyAnimeList\MalClient;
|
||||
use Jikan\Request\Reviews\RecentReviewsRequest;
|
||||
|
||||
/**
|
||||
* @implements RequestHandler<QueryTopReviewsCommand, JsonResponse>
|
||||
*/
|
||||
class QueryTopReviewsHandler implements RequestHandler
|
||||
{
|
||||
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
|
||||
*/
|
||||
public function requestClass(): string
|
||||
{
|
||||
return QueryTopReviewsCommand::class;
|
||||
}
|
||||
}
|
44
app/Features/SearchRequestHandler.php
Normal file
44
app/Features/SearchRequestHandler.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Features;
|
||||
|
||||
use App\Contracts\DataRequest;
|
||||
use App\Contracts\RequestHandler;
|
||||
use App\Services\QueryBuilderService;
|
||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||
use Illuminate\Http\Response;
|
||||
|
||||
/**
|
||||
* @template TRequest of DataRequest<TResponse>
|
||||
* @template TResponse of ResourceCollection|JsonResource|Response
|
||||
* @implements RequestHandler<TRequest, TResponse>
|
||||
*/
|
||||
abstract class SearchRequestHandler implements RequestHandler
|
||||
{
|
||||
public function __construct(
|
||||
private readonly QueryBuilderService $queryBuilderService
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function handle($request)
|
||||
{
|
||||
// note: ->all() doesn't transform the dto, all the parsed data is returned as it was parsed. (and validated)
|
||||
$requestData = collect($request->all());
|
||||
$builder = $this->queryBuilderService->query($requestData);
|
||||
$page = $requestData->get("page");
|
||||
$limit = $requestData->get("limit");
|
||||
$paginator = $this->queryBuilderService->paginateBuilder($builder, $page, $limit);
|
||||
|
||||
return $this->renderResponse($paginator);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param LengthAwarePaginator $paginator
|
||||
* @return TResponse
|
||||
*/
|
||||
protected abstract function renderResponse(LengthAwarePaginator $paginator);
|
||||
}
|
53
app/Features/UserSearchHandler.php
Normal file
53
app/Features/UserSearchHandler.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace App\Features;
|
||||
|
||||
use App\Concerns\ScraperResultCache;
|
||||
use App\Contracts\RequestHandler;
|
||||
use App\Dto\UsersSearchCommand;
|
||||
use App\Http\Resources\V4\ResultsResource;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Jikan\MyAnimeList\MalClient;
|
||||
use Jikan\Request\Search\UserSearchRequest;
|
||||
|
||||
/**
|
||||
* @implements RequestHandler<UsersSearchCommand, JsonResponse>
|
||||
*/
|
||||
final class UserSearchHandler implements RequestHandler
|
||||
{
|
||||
use ScraperResultCache;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function requestClass(): string
|
||||
{
|
||||
return UsersSearchCommand::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param UsersSearchCommand $request
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function handle($request): JsonResponse
|
||||
{
|
||||
$requestParams = collect($request->all());
|
||||
$requestFingerPrint = $request->getFingerPrint();
|
||||
$results = $this->queryFromScraperCacheByFingerPrint(
|
||||
"users",
|
||||
$requestFingerPrint,
|
||||
fn (MalClient $jikan, int $page) => $jikan->getUserSearch((new UserSearchRequest())
|
||||
->setQuery($requestParams->get("q"))
|
||||
->setGender($requestParams->get("gender"))
|
||||
->setLocation($requestParams->get("location"))
|
||||
->setMaxAge($requestParams->get("maxAge"))
|
||||
->setMinAge($requestParams->get("minAge"))
|
||||
->setPage($page)),
|
||||
$requestParams->get("page")
|
||||
);
|
||||
|
||||
return $this->prepareResponse($requestFingerPrint, $results, (new ResultsResource(
|
||||
$results->first()
|
||||
))->response());
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ namespace App\Filters;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Pipeline\Pipeline;
|
||||
use Illuminate\Support\Collection;
|
||||
use Spatie\Enum\Enum;
|
||||
|
||||
trait FilterQueryString
|
||||
{
|
||||
@ -30,8 +31,17 @@ trait FilterQueryString
|
||||
|
||||
private function _normalizeOrderBy(Collection $filters): Collection
|
||||
{
|
||||
// If DTO is not transformed then the value can be Enum type.
|
||||
// This scenario happens when we use ->all() on the DTO, instead of ->toArray().
|
||||
// However it is preferred to have the parsed values passed down, not the transformed ones.
|
||||
foreach(["order_by", "sort"] as $key) {
|
||||
if ($filters->has($key) && $filters->get($key) instanceof Enum) {
|
||||
$filters[$key] = $filters[$key]->label;
|
||||
}
|
||||
}
|
||||
|
||||
// fixme: this can be done more elegantly, for now this is here as a quick hack.
|
||||
if ($filters->offsetExists("sort") && $filters->offsetExists("order_by")) {
|
||||
if ($filters->has("sort") && $filters->has("order_by")) {
|
||||
// we put the order by field and the sort direction in one array element.
|
||||
// the OrderByClause class will explode the string by the comma and set the correct field.
|
||||
$filters["order_by"] = $filters["order_by"] . "," . $filters["sort"];
|
||||
|
@ -1,6 +1,8 @@
|
||||
<?php
|
||||
namespace App\Filters;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
trait FilterResolver
|
||||
{
|
||||
private function resolve($filterName, $values)
|
||||
@ -26,7 +28,7 @@ trait FilterResolver
|
||||
|
||||
private function isCustomFilter($filterName)
|
||||
{
|
||||
return method_exists($this, $filterName);
|
||||
return method_exists($this, $filterName) || method_exists($this, "filterBy" . ucfirst(Str::camel($filterName)));
|
||||
}
|
||||
|
||||
private function getClosure($callable, $values)
|
||||
|
@ -14,7 +14,7 @@ class GenreAnime extends JikanApiSearchableModel
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected array $filters = ["order_by", "sort"];
|
||||
protected array $filters = [];
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
|
@ -14,7 +14,7 @@ class GenreManga extends JikanApiSearchableModel
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected array $filters = ["order_by", "sort"];
|
||||
protected array $filters = [];
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Http\Controllers\V4DB;
|
||||
|
||||
use App\Contracts\Mediator;
|
||||
use App\Http\HttpHelper;
|
||||
use App\Providers\SerializerFactory;
|
||||
use Illuminate\Http\Request;
|
||||
@ -70,17 +71,20 @@ class Controller extends BaseController
|
||||
|
||||
protected string $fingerprint;
|
||||
|
||||
protected Mediator $mediator;
|
||||
|
||||
/**
|
||||
* AnimeController constructor.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param MalClient $jikan
|
||||
*/
|
||||
public function __construct(Request $request, MalClient $jikan)
|
||||
public function __construct(Request $request, MalClient $jikan, Mediator $mediator)
|
||||
{
|
||||
$this->serializer = SerializerFactory::createV4();
|
||||
$this->jikan = $jikan;
|
||||
$this->fingerprint = HttpHelper::resolveRequestFingerprint($request);
|
||||
$this->mediator = $mediator;
|
||||
}
|
||||
|
||||
protected function isExpired($request, $results) : bool
|
||||
|
@ -54,12 +54,7 @@ class MagazineController extends ControllerWithQueryBuilderProvider
|
||||
* description="Error: Bad request. When required parameters were not supplied.",
|
||||
* ),
|
||||
* )
|
||||
* @OA\Schema(
|
||||
* schema="magazines_query_orderby",
|
||||
* description="Order by magazine data",
|
||||
* type="string",
|
||||
* enum={"mal_id", "name", "count"}
|
||||
* )
|
||||
*
|
||||
*/
|
||||
public function main(Request $request): MagazineCollection
|
||||
{
|
||||
|
@ -2,19 +2,19 @@
|
||||
|
||||
namespace App\Http\Controllers\V4DB;
|
||||
|
||||
use App\Http\QueryBuilder\SearchQueryBuilderUsers;
|
||||
use App\Http\Resources\V4\AnimeCollection;
|
||||
use App\Http\Resources\V4\CharacterCollection;
|
||||
use App\Http\Resources\V4\ClubCollection;
|
||||
use App\Http\Resources\V4\MangaCollection;
|
||||
use App\Http\Resources\V4\PersonCollection;
|
||||
use App\Http\Resources\V4\ProducerCollection;
|
||||
use App\Dto\AnimeSearchCommand;
|
||||
use App\Dto\CharactersSearchCommand;
|
||||
use App\Dto\ClubSearchCommand;
|
||||
use App\Dto\MangaSearchCommand;
|
||||
use App\Dto\PeopleSearchCommand;
|
||||
use App\Dto\ProducersSearchCommand;
|
||||
use App\Dto\UsersSearchCommand;
|
||||
use App\Http\Resources\V4\ResultsResource;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Jikan\Request\User\UsernameByIdRequest;
|
||||
|
||||
class SearchController extends ControllerWithQueryBuilderProvider
|
||||
class SearchController extends Controller
|
||||
{
|
||||
/**
|
||||
* @OA\Parameter(
|
||||
@ -27,13 +27,6 @@ class SearchController extends ControllerWithQueryBuilderProvider
|
||||
* in="query",
|
||||
* @OA\Schema(type="integer")
|
||||
* ),
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="search_query_sort",
|
||||
* description="Characters Search Query Sort",
|
||||
* type="string",
|
||||
* enum={"desc","asc"}
|
||||
* )
|
||||
*/
|
||||
|
||||
/**
|
||||
@ -163,9 +156,9 @@ class SearchController extends ControllerWithQueryBuilderProvider
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
public function anime(Request $request)
|
||||
public function anime(AnimeSearchCommand $request)
|
||||
{
|
||||
return $this->preparePaginatedResponse(AnimeCollection::class, "anime", $request);
|
||||
return $this->mediator->send($request);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -289,9 +282,9 @@ class SearchController extends ControllerWithQueryBuilderProvider
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
public function manga(Request $request)
|
||||
public function manga(MangaSearchCommand $request)
|
||||
{
|
||||
return $this->preparePaginatedResponse(MangaCollection::class, "manga", $request);
|
||||
return $this->mediator->send($request);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -339,9 +332,9 @@ class SearchController extends ControllerWithQueryBuilderProvider
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
public function people(Request $request)
|
||||
public function people(PeopleSearchCommand $request)
|
||||
{
|
||||
return $this->preparePaginatedResponse(PersonCollection::class, "people", $request);
|
||||
return $this->mediator->send($request);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -391,9 +384,9 @@ class SearchController extends ControllerWithQueryBuilderProvider
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
public function character(Request $request)
|
||||
public function character(CharactersSearchCommand $request)
|
||||
{
|
||||
return $this->preparePaginatedResponse(CharacterCollection::class, "character", $request);
|
||||
return $this->mediator->send($request);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -487,35 +480,9 @@ class SearchController extends ControllerWithQueryBuilderProvider
|
||||
* },
|
||||
* ),
|
||||
*/
|
||||
public function users(Request $request)
|
||||
public function users(UsersSearchCommand $request)
|
||||
{
|
||||
$results = DB::table($this->getRouteTable($request))
|
||||
->where('request_hash', $this->fingerprint)
|
||||
->get();
|
||||
|
||||
if (
|
||||
$results->isEmpty()
|
||||
|| $this->isExpired($request, $results)
|
||||
) {
|
||||
$anime = $this->jikan->getUserSearch(
|
||||
SearchQueryBuilderUsers::query(
|
||||
$request
|
||||
)
|
||||
);
|
||||
$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
|
||||
);
|
||||
return $this->mediator->send($request);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -635,20 +602,12 @@ class SearchController extends ControllerWithQueryBuilderProvider
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
public function clubs(Request $request)
|
||||
public function clubs(ClubSearchCommand $request)
|
||||
{
|
||||
return $this->preparePaginatedResponse(ClubCollection::class, "club", $request);
|
||||
return $this->mediator->send($request);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="producers_query_orderby",
|
||||
* description="Producers Search Query Order By",
|
||||
* type="string",
|
||||
* enum={"mal_id", "count", "favorites", "established"}
|
||||
* )
|
||||
*
|
||||
* @OA\Get(
|
||||
* path="/producers",
|
||||
* operationId="getProducers",
|
||||
@ -696,8 +655,8 @@ class SearchController extends ControllerWithQueryBuilderProvider
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
public function producers(Request $request)
|
||||
public function producers(ProducersSearchCommand $request)
|
||||
{
|
||||
return $this->preparePaginatedResponse(ProducerCollection::class, "producers", $request);
|
||||
return $this->mediator->send($request);
|
||||
}
|
||||
}
|
||||
|
@ -2,18 +2,13 @@
|
||||
|
||||
namespace App\Http\Controllers\V4DB;
|
||||
|
||||
use App\Http\Resources\V4\AnimeCollection;
|
||||
use App\Http\Resources\V4\CharacterCollection;
|
||||
use App\Http\Resources\V4\MangaCollection;
|
||||
use App\Http\Resources\V4\PersonCollection;
|
||||
use App\Http\Resources\V4\ResultsResource;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Jikan\Helper\Constants;
|
||||
use Jikan\Request\Reviews\RecentReviewsRequest;
|
||||
use App\Dto\QueryTopAnimeItemsCommand;
|
||||
use App\Dto\QueryTopCharactersCommand;
|
||||
use App\Dto\QueryTopMangaItemsCommand;
|
||||
use App\Dto\QueryTopPeopleCommand;
|
||||
use App\Dto\QueryTopReviewsCommand;
|
||||
|
||||
|
||||
class TopController extends ControllerWithQueryBuilderProvider
|
||||
class TopController extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
@ -33,7 +28,7 @@ class TopController extends ControllerWithQueryBuilderProvider
|
||||
* name="filter",
|
||||
* in="query",
|
||||
* required=false,
|
||||
* @OA\Schema(type="string",enum={"airing", "upcoming", "bypopularity", "favorite"})
|
||||
* @OA\Schema(ref="#/components/schemas/top_anime_filter)
|
||||
* ),
|
||||
*
|
||||
* @OA\Parameter(
|
||||
@ -65,9 +60,9 @@ class TopController extends ControllerWithQueryBuilderProvider
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
public function anime(Request $request)
|
||||
public function anime(QueryTopAnimeItemsCommand $request)
|
||||
{
|
||||
return $this->preparePaginatedResponse(AnimeCollection::class, "top_anime", $request);
|
||||
return $this->mediator->send($request);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -87,7 +82,7 @@ class TopController extends ControllerWithQueryBuilderProvider
|
||||
* name="filter",
|
||||
* in="query",
|
||||
* required=false,
|
||||
* @OA\Schema(type="string",enum={"publishing", "upcoming", "bypopularity", "favorite"})
|
||||
* @OA\Schema(ref="#/components/schemas/top_manga_filter)
|
||||
* ),
|
||||
*
|
||||
* @OA\Parameter(ref="#/components/parameters/page"),
|
||||
@ -106,9 +101,9 @@ class TopController extends ControllerWithQueryBuilderProvider
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
public function manga(Request $request)
|
||||
public function manga(QueryTopMangaItemsCommand $request)
|
||||
{
|
||||
return $this->preparePaginatedResponse(MangaCollection::class, "top_manga", $request);
|
||||
return $this->mediator->send($request);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -133,18 +128,9 @@ class TopController extends ControllerWithQueryBuilderProvider
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
public function people(Request $request)
|
||||
public function people(QueryTopPeopleCommand $request)
|
||||
{
|
||||
$results = $this->getQueryBuilder("people", $request)
|
||||
->whereNotNull('member_favorites')
|
||||
->where('member_favorites', '>', 0)
|
||||
->orderBy('member_favorites', 'desc');
|
||||
|
||||
$results = $this->getPaginator("people", $request, $results);
|
||||
|
||||
return new PersonCollection(
|
||||
$results
|
||||
);
|
||||
return $this->mediator->send($request);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -169,18 +155,9 @@ class TopController extends ControllerWithQueryBuilderProvider
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
public function characters(Request $request)
|
||||
public function characters(QueryTopCharactersCommand $request)
|
||||
{
|
||||
$results = $this->getQueryBuilder("character", $request)
|
||||
->whereNotNull('member_favorites')
|
||||
->where('member_favorites', '>', 0)
|
||||
->orderBy('member_favorites', 'desc');
|
||||
|
||||
$results = $this->getPaginator("character", $request, $results);
|
||||
|
||||
return new CharacterCollection(
|
||||
$results
|
||||
);
|
||||
return $this->mediator->send($request);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -276,33 +253,8 @@ class TopController extends ControllerWithQueryBuilderProvider
|
||||
* ),
|
||||
* ),
|
||||
*/
|
||||
public function reviews(Request $request)
|
||||
public function reviews(QueryTopReviewsCommand $request)
|
||||
{
|
||||
$results = DB::table($this->getRouteTable($request))
|
||||
->where('request_hash', $this->fingerprint)
|
||||
->get();
|
||||
|
||||
if (
|
||||
$results->isEmpty()
|
||||
|| $this->isExpired($request, $results)
|
||||
) {
|
||||
$page = $request->get('page') ?? 1;
|
||||
$data = $this->jikan->getRecentReviews(
|
||||
new RecentReviewsRequest(Constants::RECENT_REVIEW_BEST_VOTED, $page)
|
||||
);
|
||||
$response = \json_decode($this->serializer->serialize($data, 'json'), true);
|
||||
|
||||
$results = $this->updateCache($request, $results, $response);
|
||||
}
|
||||
|
||||
$response = (new ResultsResource(
|
||||
$results->first()
|
||||
))->response();
|
||||
|
||||
return $this->prepareResponse(
|
||||
$response,
|
||||
$results,
|
||||
$request
|
||||
);
|
||||
return $this->mediator->send($request);
|
||||
}
|
||||
}
|
||||
|
@ -23,12 +23,12 @@ trait JikanApiQueryBuilder
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param T $resourceCollectionClass
|
||||
* @param class-string<T> $resourceCollectionClass
|
||||
* @param string $resourceTypeName
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return T
|
||||
*/
|
||||
protected function preparePaginatedResponse(string|object $resourceCollectionClass, string $resourceTypeName, Request $request)
|
||||
protected function preparePaginatedResponse(string $resourceCollectionClass, string $resourceTypeName, Request $request)
|
||||
{
|
||||
$results = $this->getQueryBuilder($resourceTypeName, $request);
|
||||
$paginator = $this->getPaginator($resourceTypeName, $request, $results);
|
||||
|
@ -7,13 +7,12 @@ use Illuminate\Http\Response;
|
||||
|
||||
class HttpResponse
|
||||
{
|
||||
|
||||
public static function notFound(Request $request) : Response
|
||||
{
|
||||
return response(
|
||||
\json_encode([
|
||||
'status' => 404,
|
||||
'type' => 'BadResponseException',
|
||||
'type' => 'NotFoundException',
|
||||
'message' => 'Resource not found',
|
||||
'error' => '404 on ' . $request->getUri()
|
||||
]),
|
||||
@ -33,4 +32,4 @@ class HttpResponse
|
||||
400
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ abstract class MediaSearchQueryBuilder extends SearchQueryBuilder
|
||||
{
|
||||
$parameters = parent::sanitizeParameters($parameters);
|
||||
|
||||
if (!$parameters->offsetExists("score")) {
|
||||
if (!$parameters->has("score")) {
|
||||
$parameters["score"] = 0;
|
||||
}
|
||||
|
||||
|
@ -33,17 +33,10 @@ class GenreCollection extends ResourceCollection
|
||||
* ref="#/components/schemas/genre"
|
||||
* ),
|
||||
* ),
|
||||
* ),
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="genre_query_filter",
|
||||
* description="Filter genres by type",
|
||||
* type="string",
|
||||
* enum={"genres","explicit_genres", "themes", "demographics"}
|
||||
* )
|
||||
*/
|
||||
public function toArray($request)
|
||||
{
|
||||
return $this->collection;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App;
|
||||
|
||||
use App\Concerns\FilteredByLetter;
|
||||
use Jikan\Jikan;
|
||||
use Jikan\Request\Magazine\MagazinesRequest;
|
||||
|
||||
@ -11,7 +12,8 @@ use Jikan\Request\Magazine\MagazinesRequest;
|
||||
*/
|
||||
class Magazine extends JikanApiSearchableModel
|
||||
{
|
||||
protected array $filters = ["order_by", "sort"];
|
||||
use FilteredByLetter;
|
||||
protected array $filters = ["order_by", "sort", "letter"];
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
@ -29,6 +31,7 @@ class Magazine extends JikanApiSearchableModel
|
||||
*/
|
||||
protected $table = 'magazines';
|
||||
|
||||
protected ?string $displayNameFieldName = "name";
|
||||
|
||||
/**
|
||||
* The attributes excluded from the model's JSON form.
|
||||
|
@ -2,7 +2,10 @@
|
||||
|
||||
namespace App;
|
||||
|
||||
use App\Concerns\FilteredByLetter;
|
||||
use App\Concerns\MediaFilters;
|
||||
use App\Http\HttpHelper;
|
||||
use Carbon\CarbonImmutable;
|
||||
use Database\Factories\MangaFactory;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Jikan\Jikan;
|
||||
@ -11,11 +14,9 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
|
||||
class Manga extends JikanApiSearchableModel
|
||||
{
|
||||
use HasFactory;
|
||||
use HasFactory, MediaFilters, FilteredByLetter;
|
||||
|
||||
// note that here we skip "score", "min_score", "max_score", "rating" and others because they need special logic
|
||||
// to set the correct filtering on the ORM.
|
||||
protected array $filters = ["order_by", "status", "type", "sort"];
|
||||
protected array $filters = ["order_by", "status", "type", "sort", "max_score", "min_score", "score", "start_date", "end_date", "magazine", "magazines", "letter"];
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
@ -34,6 +35,7 @@ class Manga extends JikanApiSearchableModel
|
||||
*/
|
||||
protected $appends = [];
|
||||
|
||||
protected ?string $displayNameFieldName = "title";
|
||||
|
||||
/**
|
||||
* The table associated with the model.
|
||||
@ -51,6 +53,48 @@ class Manga extends JikanApiSearchableModel
|
||||
'_id', 'expiresAt', 'request_hash'
|
||||
];
|
||||
|
||||
/** @noinspection PhpUnused */
|
||||
public function filterByStartDate(\Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder $query, CarbonImmutable $date): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder
|
||||
{
|
||||
return $query->where("published.from", $date->setTime(0, 0)->toAtomString());
|
||||
}
|
||||
|
||||
/** @noinspection PhpUnused */
|
||||
public function filterByEndDate(\Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder $query, CarbonImmutable $date): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder
|
||||
{
|
||||
return $query->where("published.to", $date->setTime(0, 0)->toAtomString());
|
||||
}
|
||||
|
||||
public function filterByMagazine(\Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder $query, string $value): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder
|
||||
{
|
||||
if (empty($value)) {
|
||||
return $query;
|
||||
}
|
||||
|
||||
$magazine = (int)$value;
|
||||
return $query
|
||||
->orWhere('serializations.mal_id', $magazine);
|
||||
}
|
||||
|
||||
/** @noinspection PhpUnused */
|
||||
public function filterByMagazines(\Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder $query, string $value): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder
|
||||
{
|
||||
if (empty($value)) {
|
||||
return $query;
|
||||
}
|
||||
|
||||
$magazines = explode(',', $value);
|
||||
foreach ($magazines as $magazine) {
|
||||
if (empty($magazine)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$query = $this->filterByMagazine($query, $value);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
public static function scrape(int $id)
|
||||
{
|
||||
$data = app('JikanParser')->getManga(new MangaRequest($id));
|
||||
|
@ -2,12 +2,13 @@
|
||||
|
||||
namespace App;
|
||||
|
||||
use Jenssegers\Mongodb\Eloquent\Model;
|
||||
use App\Concerns\FilteredByLetter;
|
||||
use Jikan\Request\Producer\ProducerRequest;
|
||||
|
||||
class Producers extends JikanApiSearchableModel
|
||||
{
|
||||
protected array $filters = ["order_by", "sort"];
|
||||
use FilteredByLetter;
|
||||
protected array $filters = ["order_by", "sort", "letter"];
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
@ -25,6 +26,8 @@ class Producers extends JikanApiSearchableModel
|
||||
*/
|
||||
protected $table = 'producers';
|
||||
|
||||
protected ?string $displayNameFieldName = "titles.0.title";
|
||||
|
||||
/**
|
||||
* The attributes excluded from the model's JSON form.
|
||||
*
|
||||
|
@ -2,12 +2,13 @@
|
||||
|
||||
namespace App;
|
||||
|
||||
use Jenssegers\Mongodb\Eloquent\Model;
|
||||
use App\Concerns\FilteredByLetter;
|
||||
use Jikan\Request\User\UserProfileRequest;
|
||||
|
||||
class Profile extends JikanApiSearchableModel
|
||||
{
|
||||
protected array $filters = ["order_by", "sort"];
|
||||
use FilteredByLetter;
|
||||
protected array $filters = ["order_by", "sort", "letter"];
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
@ -25,6 +26,8 @@ class Profile extends JikanApiSearchableModel
|
||||
*/
|
||||
protected $table = 'users';
|
||||
|
||||
protected ?string $displayNameFieldName = "username";
|
||||
|
||||
/**
|
||||
* The attributes excluded from the model's JSON form.
|
||||
*
|
||||
|
@ -2,6 +2,32 @@
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Contracts\AnimeRepository;
|
||||
use App\Contracts\CharacterRepository;
|
||||
use App\Contracts\ClubRepository;
|
||||
use App\Contracts\MagazineRepository;
|
||||
use App\Contracts\MangaRepository;
|
||||
use App\Contracts\Mediator;
|
||||
use App\Contracts\PeopleRepository;
|
||||
use App\Contracts\ProducerRepository;
|
||||
use App\Contracts\Repository;
|
||||
use App\Contracts\RequestHandler;
|
||||
use App\Contracts\UnitOfWork;
|
||||
use App\Contracts\UserRepository;
|
||||
use App\Dto\QueryTopPeopleCommand;
|
||||
use App\Features\AnimeGenreListHandler;
|
||||
use App\Features\AnimeSearchHandler;
|
||||
use App\Features\CharacterSearchHandler;
|
||||
use App\Features\ClubSearchHandler;
|
||||
use App\Features\MagazineSearchHandler;
|
||||
use App\Features\MangaSearchHandler;
|
||||
use App\Features\PeopleSearchHandler;
|
||||
use App\Features\ProducerSearchHandler;
|
||||
use App\Features\QueryTopAnimeItemsHandler;
|
||||
use App\Features\QueryTopCharactersHandler;
|
||||
use App\Features\QueryTopMangaItemsHandler;
|
||||
use App\Features\QueryTopReviewsHandler;
|
||||
use App\Features\UserSearchHandler;
|
||||
use App\GenreAnime;
|
||||
use App\GenreManga;
|
||||
use App\Http\QueryBuilder\AnimeSearchQueryBuilder;
|
||||
@ -16,10 +42,29 @@ use App\Macros\To2dArrayWithDottedKeys;
|
||||
use App\Magazine;
|
||||
use App\Mixins\ScoutBuilderMixin;
|
||||
use App\Producers;
|
||||
use App\Repositories\AnimeGenresRepository;
|
||||
use App\Repositories\DefaultAnimeRepository;
|
||||
use App\Repositories\DefaultCharacterRepository;
|
||||
use App\Repositories\DefaultClubRepository;
|
||||
use App\Repositories\DefaultMagazineRepository;
|
||||
use App\Repositories\DefaultMangaRepository;
|
||||
use App\Repositories\DefaultPeopleRepository;
|
||||
use App\Repositories\DefaultProducerRepository;
|
||||
use App\Repositories\DefaultUserRepository;
|
||||
use App\Repositories\MangaGenresRepository;
|
||||
use App\Services\DefaultQueryBuilderService;
|
||||
use App\Services\DefaultScoutSearchService;
|
||||
use App\Services\ElasticScoutSearchService;
|
||||
use App\Services\EloquentBuilderPaginatorService;
|
||||
use App\Services\MongoSearchService;
|
||||
use App\Services\ScoutBuilderPaginatorService;
|
||||
use App\Services\ScoutSearchService;
|
||||
use App\Services\SearchEngineSearchService;
|
||||
use App\Services\TypeSenseScoutSearchService;
|
||||
use App\Support\DefaultMediator;
|
||||
use App\Support\JikanUnitOfWork;
|
||||
use Illuminate\Contracts\Container\BindingResolutionException;
|
||||
use Illuminate\Contracts\Foundation\Application;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Illuminate\Support\Collection;
|
||||
use Laravel\Scout\Builder as ScoutBuilder;
|
||||
@ -48,14 +93,17 @@ class AppServiceProvider extends ServiceProvider
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
$this->app->singleton(ScoutSearchService::class, function($app) {
|
||||
$scoutDriver = $this->getSearchIndexDriver($app);
|
||||
return match ($scoutDriver) {
|
||||
"typesense" => new TypeSenseScoutSearchService(),
|
||||
"Matchish\ScoutElasticSearch\Engines\ElasticSearchEngine" => new ElasticScoutSearchService(),
|
||||
default => new DefaultScoutSearchService()
|
||||
};
|
||||
});
|
||||
// $this->app->singleton(ScoutSearchService::class, function($app) {
|
||||
// $scoutDriver = $this->getSearchIndexDriver($app);
|
||||
//
|
||||
// return match ($scoutDriver) {
|
||||
// "typesense" => new TypeSenseScoutSearchService(),
|
||||
// "Matchish\ScoutElasticSearch\Engines\ElasticSearchEngine" => new ElasticScoutSearchService(),
|
||||
// default => new DefaultScoutSearchService()
|
||||
// };
|
||||
// });
|
||||
|
||||
// todo: remove SearchQueryBuilders
|
||||
|
||||
$queryBuilders = [
|
||||
AnimeSearchQueryBuilder::class,
|
||||
@ -124,9 +172,139 @@ class AppServiceProvider extends ServiceProvider
|
||||
});
|
||||
}
|
||||
|
||||
private function registerModelRepositories()
|
||||
{
|
||||
// note: We deliberately not included here any of the GenreRepository implementations.
|
||||
// We don't want to bind them to an abstract symbol.
|
||||
$repositories = [
|
||||
AnimeRepository::class => DefaultAnimeRepository::class,
|
||||
MangaRepository::class => DefaultMangaRepository::class,
|
||||
CharacterRepository::class => DefaultCharacterRepository::class,
|
||||
ClubRepository::class => DefaultClubRepository::class,
|
||||
MagazineRepository::class => DefaultMagazineRepository::class,
|
||||
ProducerRepository::class => DefaultProducerRepository::class,
|
||||
PeopleRepository::class => DefaultPeopleRepository::class,
|
||||
UserRepository::class => DefaultUserRepository::class,
|
||||
];
|
||||
|
||||
foreach ($repositories as $abstract => $concrete) {
|
||||
$this->app->singleton($abstract, $concrete);
|
||||
}
|
||||
|
||||
$this->app->singleton(AnimeGenresRepository::class);
|
||||
$this->app->singleton(MangaGenresRepository::class);
|
||||
|
||||
$this->app->singleton(UnitOfWork::class, JikanUnitOfWork::class);
|
||||
}
|
||||
|
||||
private function registerRequestHandlers()
|
||||
{
|
||||
/*
|
||||
* This bit is about a "mediator" pattern for handling requests.
|
||||
*/
|
||||
$this->app->singleton(Mediator::class, DefaultMediator::class);
|
||||
/*
|
||||
* Each request is represented as a data transfer object, and spatie/laravel-data package's service provider
|
||||
* registers them in the ioc container. For each request there is a request handler.
|
||||
* Validation for requests is specified in the DTOs.
|
||||
* Querying/Filtering entirely happens on the model side.
|
||||
* 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)
|
||||
* Every request handler gets a dedicated instance of QueryBuilderService which will be configured with a
|
||||
* specific repository instance.
|
||||
*/
|
||||
$this->app->when(DefaultMediator::class)
|
||||
->needs(RequestHandler::class)
|
||||
->give(function (Application $app) {
|
||||
$searchIndexesEnabled = $this->getSearchIndexesEnabledConfig($app);
|
||||
/**
|
||||
* @var UnitOfWork $unitOfWorkInstance
|
||||
*/
|
||||
$unitOfWorkInstance = $app->make(UnitOfWork::class);
|
||||
$searchRequestHandlersDescriptors = [
|
||||
AnimeSearchHandler::class => $unitOfWorkInstance->anime(),
|
||||
MangaSearchHandler::class => $unitOfWorkInstance->manga(),
|
||||
CharacterSearchHandler::class => $unitOfWorkInstance->characters(),
|
||||
PeopleSearchHandler::class => $unitOfWorkInstance->people(),
|
||||
ClubSearchHandler::class => $unitOfWorkInstance->clubs(),
|
||||
MagazineSearchHandler::class => $unitOfWorkInstance->magazines(),
|
||||
ProducerSearchHandler::class => $unitOfWorkInstance->producers(),
|
||||
UserSearchHandler::class => $unitOfWorkInstance->users()
|
||||
];
|
||||
$requestHandlers = [];
|
||||
foreach ($searchRequestHandlersDescriptors as $handlerClass => $repositoryInstance) {
|
||||
$requestHandlers[] = $this->app->make($handlerClass, [
|
||||
$app->make(DefaultQueryBuilderService::class, [
|
||||
static::makeSearchService($app, $searchIndexesEnabled, $repositoryInstance),
|
||||
$app->make($searchIndexesEnabled ? ScoutBuilderPaginatorService::class : EloquentBuilderPaginatorService::class)
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
$queryTopItemsDescriptors = [
|
||||
QueryTopAnimeItemsHandler::class => $unitOfWorkInstance->anime(),
|
||||
QueryTopMangaItemsHandler::class => $unitOfWorkInstance->manga(),
|
||||
QueryTopCharactersHandler::class => $unitOfWorkInstance->characters(),
|
||||
QueryTopPeopleCommand::class => $unitOfWorkInstance->people(),
|
||||
];
|
||||
|
||||
foreach ($queryTopItemsDescriptors as $handlerClass => $repositoryInstance) {
|
||||
$requestHandlers[] = $this->app->make($handlerClass, [
|
||||
$repositoryInstance,
|
||||
// top queries don't use the search engine, so it's enough for them to use eloquent paginator
|
||||
$this->app->make(EloquentBuilderPaginatorService::class)
|
||||
]);
|
||||
}
|
||||
|
||||
$genreRequestHandlerDescriptors = [
|
||||
AnimeGenreListHandler::class => $unitOfWorkInstance->animeGenres(),
|
||||
MangaGenresRepository::class => $unitOfWorkInstance->mangaGenres()
|
||||
];
|
||||
|
||||
foreach ($genreRequestHandlerDescriptors as $handlerClass => $repositoryInstance) {
|
||||
$requestHandlers[] = $this->app->make($handlerClass, [$repositoryInstance]);
|
||||
}
|
||||
|
||||
$requestHandlers[] = $this->app->make(QueryTopReviewsHandler::class);
|
||||
|
||||
return $requestHandlers;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a search service instance.
|
||||
* Search service knows how to do a full-text search on the database query builder instance.
|
||||
* @throws BindingResolutionException
|
||||
*/
|
||||
private static function makeSearchService(Application $app, bool $searchIndexesEnabled, Repository $repositoryInstance)
|
||||
{
|
||||
return $searchIndexesEnabled ? $app->make( SearchEngineSearchService::class, [
|
||||
static::makeScoutSearchService($app, $repositoryInstance), $repositoryInstance
|
||||
]) : $app->make(MongoSearchService::class, [$repositoryInstance]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a scout search service instance.
|
||||
* Scout search service knows about the configured search engine's implementation details.
|
||||
* E.g. per search request configuration.
|
||||
* @throws BindingResolutionException
|
||||
*/
|
||||
private static function makeScoutSearchService(Application $app, Repository $repositoryInstance)
|
||||
{
|
||||
// todo: cache result
|
||||
$scoutDriver = static::getSearchIndexDriver($app);
|
||||
$serviceClass = match ($scoutDriver) {
|
||||
"typesense" => TypeSenseScoutSearchService::class,
|
||||
"Matchish\ScoutElasticSearch\Engines\ElasticSearchEngine" => ElasticScoutSearchService::class,
|
||||
default => DefaultScoutSearchService::class
|
||||
};
|
||||
|
||||
return $app->make($serviceClass, [$repositoryInstance]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \ReflectionException
|
||||
* @throws \Illuminate\Contracts\Container\BindingResolutionException
|
||||
* @throws BindingResolutionException
|
||||
* @return void
|
||||
*/
|
||||
private function registerMacros(): void
|
||||
@ -158,7 +336,7 @@ class AppServiceProvider extends ServiceProvider
|
||||
return $this->getSearchIndexDriver($app) != "null";
|
||||
}
|
||||
|
||||
private function getSearchIndexDriver($app): string
|
||||
private static function getSearchIndexDriver($app): string
|
||||
{
|
||||
return $app["config"]->get("scout.driver");
|
||||
}
|
||||
|
12
app/Providers/JikanEnumServiceProvider.php
Normal file
12
app/Providers/JikanEnumServiceProvider.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
use Spatie\Enum\Laravel\EnumServiceProvider;
|
||||
|
||||
class JikanEnumServiceProvider extends EnumServiceProvider
|
||||
{
|
||||
protected function registerRouteBindingMacro(): void
|
||||
{
|
||||
// noop
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user