wip - refactor finished

This commit is contained in:
pushrbx 2023-01-25 21:41:27 +00:00
parent 4a25c30d7d
commit 1e302fb62c
41 changed files with 238 additions and 2495 deletions

View File

@ -0,0 +1,17 @@
<?php
namespace App\Dto;
use App\Concerns\HasRequestFingerprint;
use App\Contracts\DataRequest;
use Illuminate\Http\JsonResponse;
use Spatie\LaravelData\Data;
/**
* @implements DataRequest<JsonResponse>
*/
final class QueryPopularEpisodesCommand extends Data implements DataRequest
{
use HasRequestFingerprint;
}

View File

@ -0,0 +1,17 @@
<?php
namespace App\Dto;
use App\Concerns\HasRequestFingerprint;
use App\Contracts\DataRequest;
use Illuminate\Http\JsonResponse;
use Spatie\LaravelData\Data;
/**
* @implements DataRequest<JsonResponse>
*/
final class QueryPopularPromoVideosCommand extends Data implements DataRequest
{
use HasRequestFingerprint;
}

View File

@ -0,0 +1,17 @@
<?php
namespace App\Dto;
use App\Concerns\HasRequestFingerprint;
use App\Contracts\DataRequest;
use Illuminate\Http\JsonResponse;
use Spatie\LaravelData\Data;
/**
* @implements DataRequest<JsonResponse>
*/
final class QueryRecentlyAddedEpisodesCommand extends Data implements DataRequest
{
use HasRequestFingerprint;
}

View File

@ -0,0 +1,18 @@
<?php
namespace App\Dto;
use App\Concerns\HasRequestFingerprint;
use App\Contracts\DataRequest;
use App\Dto\Concerns\HasPageParameter;
use Illuminate\Http\JsonResponse;
use Spatie\LaravelData\Data;
/**
* @implements DataRequest<JsonResponse>
*/
final class QueryRecentlyAddedPromoVideosCommand extends Data implements DataRequest
{
use HasRequestFingerprint, HasPageParameter;
}

View File

@ -9,12 +9,14 @@ use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;
use Jikan\Exception\BadResponseException;
use Jikan\Exception\BadResponseException as JikanBadResponseException;
use GuzzleHttp\Exception\BadResponseException;
use Jikan\Exception\ParserException;
use Laravel\Lumen\Exceptions\Handler as ExceptionHandler;
use Predis\Connection\ConnectionException;
use Symfony\Component\HttpClient\Exception\TimeoutException;
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
use Symfony\Component\HttpClient\Exception\TransportException;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\HttpException;
@ -119,7 +121,7 @@ class Handler extends ExceptionHandler
// BadResponseException from Jikan PHP API
// This is basically the response MyAnimeList returns to Jikan
if ($e instanceof BadResponseException) {
if ($e instanceof BadResponseException || $e instanceof JikanBadResponseException) {
switch ($e->getCode()) {
case 404:
// $this->set404Cache($request, $e);
@ -176,6 +178,16 @@ class Handler extends ExceptionHandler
], 408);
}
if ($e instanceof TransportException) {
return response()
->json([
'status' => 500,
'type' => 'TransportException',
'message' => 'Request to MyAnimeList.net has failed. The upstream server has returned a non-successful status code.',
'error' => $e->getMessage()
], 500);
}
if ($e instanceof Exception && $e->getMessage() === "Undefined index: url") {
event(new SourceHeartbeatEvent(SourceHeartbeatEvent::BAD_HEALTH, $e->getCode()));

View File

@ -0,0 +1,29 @@
<?php
namespace App\Features;
use App\Dto\QueryPopularEpisodesCommand;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Collection;
use App\Support\CachedData;
use Jikan\MyAnimeList\MalClient;
use Jikan\Request\Watch\PopularEpisodesRequest;
/**
* @extends RequestHandlerWithScraperCache<QueryPopularEpisodesCommand, JsonResponse>
*/
final class QueryPopularEpisodesHandler extends RequestHandlerWithScraperCache
{
public function requestClass(): string
{
return QueryPopularEpisodesCommand::class;
}
protected function getScraperData(string $requestFingerPrint, Collection $requestParams): CachedData
{
return $this->scraperService->findList(
$requestFingerPrint,
fn(MalClient $jikan, ?int $page = null) => $jikan->getPopularEpisodes(new PopularEpisodesRequest())
);
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace App\Features;
use App\Dto\QueryPopularPromoVideosCommand;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Collection;
use App\Support\CachedData;
use Jikan\MyAnimeList\MalClient;
use Jikan\Request\Watch\PopularPromotionalVideosRequest;
/**
* @extends RequestHandlerWithScraperCache<QueryPopularPromoVideosCommand, JsonResponse>
*/
final class QueryPopularPromoVideosHandler extends RequestHandlerWithScraperCache
{
public function requestClass(): string
{
return QueryPopularPromoVideosCommand::class;
}
protected function getScraperData(string $requestFingerPrint, Collection $requestParams): CachedData
{
return $this->scraperService->findList(
$requestFingerPrint,
fn(MalClient $jikan, ?int $page = null) => $jikan->getPopularPromotionalVideos(new PopularPromotionalVideosRequest())
);
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace App\Features;
use App\Dto\QueryRecentlyAddedEpisodesCommand;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Collection;
use App\Support\CachedData;
use Jikan\MyAnimeList\MalClient;
use Jikan\Request\Watch\RecentEpisodesRequest;
/**
* @extends RequestHandlerWithScraperCache<QueryRecentlyAddedEpisodesCommand, JsonResponse>
*/
final class QueryRecentlyAddedEpisodesHandler extends RequestHandlerWithScraperCache
{
public function requestClass(): string
{
return QueryRecentlyAddedEpisodesCommand::class;
}
protected function getScraperData(string $requestFingerPrint, Collection $requestParams): CachedData
{
return $this->scraperService->findList(
$requestFingerPrint,
fn(MalClient $jikan, ?int $page = null) => $jikan->getRecentEpisodes(new RecentEpisodesRequest())
);
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace App\Features;
use App\Dto\QueryRecentlyAddedPromoVideosCommand;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Collection;
use App\Support\CachedData;
use Jikan\MyAnimeList\MalClient;
use Jikan\Request\Watch\RecentPromotionalVideosRequest;
/**
* @extends RequestHandlerWithScraperCache<QueryRecentlyAddedPromoVideosCommand, JsonResponse>
*/
final class QueryRecentlyAddedPromoVideosHandler extends RequestHandlerWithScraperCache
{
public function requestClass(): string
{
return QueryRecentlyAddedPromoVideosCommand::class;
}
protected function getScraperData(string $requestFingerPrint, Collection $requestParams): CachedData
{
return $this->scraperService->findList(
$requestFingerPrint,
fn(MalClient $jikan, ?int $page = null) => $jikan->getRecentPromotionalVideos(new RecentPromotionalVideosRequest($page)),
$requestParams->get("page", 1)
);
}
}

View File

@ -47,16 +47,6 @@ class Controller extends BaseController
* )
*/
/**
* @var Serializer
*/
protected Serializer $serializer;
/**
* @var MalClient
*/
protected MalClient $jikan;
/**
* @var Request
*/
@ -67,140 +57,18 @@ class Controller extends BaseController
*/
private array $response;
protected bool $expired = false;
protected string $fingerprint;
protected Mediator $mediator;
/**
* AnimeController constructor.
*
* @param Request $request
* @param MalClient $jikan
* @param Mediator $mediator
*/
public function __construct(Request $request, MalClient $jikan, Mediator $mediator)
public function __construct(Mediator $mediator)
{
$this->serializer = SerializerFactory::createV4();
$this->jikan = $jikan;
$this->fingerprint = HttpHelper::resolveRequestFingerprint($request);
$this->mediator = $mediator;
}
protected function isExpired($request, $results) : bool
{
$lastModified = $this->getLastModified($results);
if ($lastModified === null) {
return true;
}
$routeName = HttpHelper::getRouteName($request);
$expiry = (int) config("controller.{$routeName}.ttl") + $lastModified;
if (time() > $expiry) {
return true;
}
return false;
}
protected function getExpiry($results, $request)
{
$modifiedAt = $this->getLastModified($results);
$routeName = HttpHelper::getRouteName($request);
return (int) config("controller.{$routeName}.ttl") + $modifiedAt;
}
protected function getTtl($results, $request)
{
$routeName = HttpHelper::getRouteName($request);
return (int) config("controller.{$routeName}.ttl");
}
protected function getLastModified($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 serialize($data) : array
{
return \json_decode(
$this->serializer->serialize($data, 'json')
);
}
protected function getRouteName($request) : string
{
return HttpHelper::getRouteName($request);
}
protected function getRouteTable($request) : string
{
return config("controller.{$this->getRouteName($request)}.table_name");
}
protected function prepareResponse($response, $results, $request)
{
return $response
->header('X-Request-Fingerprint', $this->fingerprint)
->setTtl($this->getTtl($results, $request))
->setExpires(
(new \DateTimeImmutable())->setTimestamp(
$this->getExpiry($results, $request)
)
)
->setLastModified(
(new \DateTimeImmutable())->setTimestamp(
$this->getLastModified($results)
)
);
}
protected function updateCache($request, $results, $response)
{
// If resource doesn't exist, prepare meta
if ($results->isEmpty()) {
$meta = [
'createdAt' => new UTCDateTime(),
'modifiedAt' => new UTCDateTime(),
'request_hash' => $this->fingerprint
];
}
// Update `modifiedAt` meta
$meta['modifiedAt'] = new UTCDateTime();
// join meta data with response
$response = $meta + $response;
// insert cache if resource doesn't exist
if ($results->isEmpty()) {
DB::table($this->getRouteTable($request))
->insert($response);
}
// update cache if resource exists
if ($this->isExpired($request, $results)) {
DB::table($this->getRouteTable($request))
->where('request_hash', $this->fingerprint)
->update($response);
}
// return results
return DB::table($this->getRouteTable($request))
->where('request_hash', $this->fingerprint)
->get();
}
/**
* @param array $response
* @return array

View File

@ -2,18 +2,10 @@
namespace App\Http\Controllers\V4DB;
use App\Http\HttpHelper;
use App\Http\HttpResponse;
use App\Http\Resources\V4\ResultsResource;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Jikan\Helper\Constants;
use Jikan\Request\Anime\AnimeNewsRequest;
use Jikan\Request\Watch\PopularEpisodesRequest;
use Jikan\Request\Watch\PopularPromotionalVideosRequest;
use Jikan\Request\Watch\RecentEpisodesRequest;
use Jikan\Request\Watch\RecentPromotionalVideosRequest;
use MongoDB\BSON\UTCDateTime;
use App\Dto\QueryPopularEpisodesCommand;
use App\Dto\QueryPopularPromoVideosCommand;
use App\Dto\QueryRecentlyAddedEpisodesCommand;
use App\Dto\QueryRecentlyAddedPromoVideosCommand;
class WatchController extends Controller
{
@ -24,8 +16,6 @@ class WatchController extends Controller
* operationId="getWatchRecentEpisodes",
* tags={"watch"},
*
* @OA\Parameter(ref="#/components/parameters/limit"),
*
* @OA\Response(
* response="200",
* description="Returns Recently Added Episodes",
@ -97,31 +87,9 @@ class WatchController extends Controller
* },
* ),
*/
public function recentEpisodes(Request $request)
public function recentEpisodes(QueryRecentlyAddedEpisodesCommand $command)
{
$results = DB::table($this->getRouteTable($request))
->where('request_hash', $this->fingerprint)
->get();
if (
$results->isEmpty()
|| $this->isExpired($request, $results)
) {
$items = $this->jikan->getRecentEpisodes(new RecentEpisodesRequest());
$response = \json_decode($this->serializer->serialize($items, 'json'), true);
$results = $this->updateCache($request, $results, $response);
}
$response = (new ResultsResource(
$results->first()
))->response();
return $this->prepareResponse(
$response,
$results,
$request
);
return $this->mediator->send($command);
}
/**
@ -130,8 +98,6 @@ class WatchController extends Controller
* operationId="getWatchPopularEpisodes",
* tags={"watch"},
*
* @OA\Parameter(ref="#/components/parameters/limit"),
*
* @OA\Response(
* response="200",
* description="Returns Popular Episodes",
@ -145,31 +111,9 @@ class WatchController extends Controller
* ),
* ),
*/
public function popularEpisodes(Request $request)
public function popularEpisodes(QueryPopularEpisodesCommand $command)
{
$results = DB::table($this->getRouteTable($request))
->where('request_hash', $this->fingerprint)
->get();
if (
$results->isEmpty()
|| $this->isExpired($request, $results)
) {
$items = $this->jikan->getPopularEpisodes(new PopularEpisodesRequest());
$response = \json_decode($this->serializer->serialize($items, 'json'), true);
$results = $this->updateCache($request, $results, $response);
}
$response = (new ResultsResource(
$results->first()
))->response();
return $this->prepareResponse(
$response,
$results,
$request
);
return $this->mediator->send($command);
}
/**
@ -178,6 +122,8 @@ class WatchController extends Controller
* operationId="getWatchRecentPromos",
* tags={"watch"},
*
* @OA\Parameter(ref="#/components/parameters/page"),
*
* @OA\Response(
* response="200",
* description="Returns Recently Added Promotional Videos",
@ -235,32 +181,9 @@ class WatchController extends Controller
* },
* ),
*/
public function recentPromos(Request $request)
public function recentPromos(QueryRecentlyAddedPromoVideosCommand $command)
{
$results = DB::table($this->getRouteTable($request))
->where('request_hash', $this->fingerprint)
->get();
if (
$results->isEmpty()
|| $this->isExpired($request, $results)
) {
$page = $request->get('page') ?? 1;
$items = $this->jikan->getRecentPromotionalVideos(new RecentPromotionalVideosRequest($page));
$response = \json_decode($this->serializer->serialize($items, 'json'), true);
$results = $this->updateCache($request, $results, $response);
}
$response = (new ResultsResource(
$results->first()
))->response();
return $this->prepareResponse(
$response,
$results,
$request
);
return $this->mediator->send($command);
}
/**
@ -269,8 +192,6 @@ class WatchController extends Controller
* operationId="getWatchPopularPromos",
* tags={"watch"},
*
* @OA\Parameter(ref="#/components/parameters/limit"),
*
* @OA\Response(
* response="200",
* description="Returns Popular Promotional Videos",
@ -284,31 +205,9 @@ class WatchController extends Controller
* ),
* ),
*/
public function popularPromos(Request $request)
public function popularPromos(QueryPopularPromoVideosCommand $command)
{
$results = DB::table($this->getRouteTable($request))
->where('request_hash', $this->fingerprint)
->get();
if (
$results->isEmpty()
|| $this->isExpired($request, $results)
) {
$items = $this->jikan->getPopularPromotionalVideos(new PopularPromotionalVideosRequest());
$response = \json_decode($this->serializer->serialize($items, 'json'), true);
$results = $this->updateCache($request, $results, $response);
}
$response = (new ResultsResource(
$results->first()
))->response();
return $this->prepareResponse(
$response,
$results,
$request
);
return $this->mediator->send($command);
}
}

View File

@ -3,6 +3,7 @@
namespace App\Http;
use Illuminate\Http\Request;
use Illuminate\Support\Env;
class HttpHelper
{
@ -29,7 +30,7 @@ class HttpHelper
public static function requestCacheExpiry(string $requestType): int
{
$requestType = strtoupper($requestType);
return (int) (env("CACHE_{$requestType}_EXPIRE") ?? env('CACHE_DEFAULT_EXPIRE'));
return (int) (Env::get("CACHE_{$requestType}_EXPIRE") ?? Env::get('CACHE_DEFAULT_EXPIRE'));
}
public static function requestAPIVersion(Request $request) : int
@ -94,10 +95,4 @@ class HttpHelper
{
return sha1($request->getRequestUri());
}
public static function getRouteTable($request) : string
{
$routeName = HttpHelper::getRouteName($request);
return config("controller.{$routeName}.table_name");
}
}

View File

@ -1,157 +0,0 @@
<?php
namespace App\Http\QueryBuilder;
use App\Anime;
use Illuminate\Support\Collection;
class AnimeSearchQueryBuilder extends MediaSearchQueryBuilder
{
protected array $parameterNames = ["producer", "producers", "rating"];
protected string $displayNameFieldName = "title";
/**
* @OA\Schema(
* schema="anime_search_query_type",
* description="Available Anime types",
* type="string",
* enum={"tv","movie","ova","special","ona","music"}
* )
*/
const MAP_TYPES = [
'tv' => 'TV',
'movie' => 'Movie',
'ova' => 'OVA',
'special' => 'Special',
'ona' => 'ONA',
'music' => 'Music'
];
/**
* @OA\Schema(
* schema="anime_search_query_status",
* description="Available Anime statuses",
* type="string",
* enum={"airing","complete","upcoming"}
* )
*/
const MAP_STATUS = [
'airing' => 'Currently Airing',
'complete' => 'Finished Airing',
'upcoming' => 'Not yet aired',
];
/**
* @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"}
* )
*/
const MAP_RATING = [
'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'
];
/**
* @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" }
* )
*/
const ORDER_BY = [
'start_date' => 'aired.from',
'end_date' => 'aired.to',
'episodes' => 'episodes',
'rating' => 'rating',
'type' => 'type',
];
protected function buildQuery(Collection $requestParameters, \Illuminate\Database\Eloquent\Builder|\Laravel\Scout\Builder $results): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder
{
$builder = parent::buildQuery($requestParameters, $results);
$rating = $requestParameters->get("rating");
$producer = $requestParameters->get("producer");
$producers = $requestParameters->get("producers");
if (!is_null($rating)) {
$builder = $builder->where('rating', $this->mapRating($rating));
}
if (!is_null($producer)) $producers = $producer;
if (!is_null($producers)) {
$producers = explode(',', $producers);
foreach ($producers as $producer) {
if (empty($producer)) {
continue;
}
$producer = (int)$producer;
$results = $results
->orWhere('producers.mal_id', $producer)
->orWhere('licensors.mal_id', $producer)
->orWhere('studios.mal_id', $producer);
}
}
return $builder;
}
protected function filterByStartDate(\Illuminate\Database\Eloquent\Builder|\Laravel\Scout\Builder $builder, string $startDate): \Illuminate\Database\Eloquent\Builder|\Laravel\Scout\Builder
{
return $builder->where('aired.from', '>=', $startDate);
}
protected function filterByEndDate(\Illuminate\Database\Eloquent\Builder|\Laravel\Scout\Builder $builder, string $endDate): \Illuminate\Database\Eloquent\Builder|\Laravel\Scout\Builder
{
return $builder->where('aired.to', '<=', $endDate);
}
protected function getStatusMap(): array
{
return self::MAP_STATUS;
}
protected function getTypeMap(): array
{
return self::MAP_TYPES;
}
protected function getModelClass(): object|string
{
return Anime::class;
}
protected function getOrderByFieldMap(): array
{
$map = parent::getOrderByFieldMap();
return array_merge($map, self::ORDER_BY);
}
protected function getAdultRating(): string
{
return self::MAP_RATING['rx'];
}
public function getIdentifier(): string
{
return "anime";
}
protected function mapRating(?string $rating = null): ?string
{
$rating = strtolower($rating);
return self::MAP_RATING[$rating] ?? null;
}
}

View File

@ -1,45 +0,0 @@
<?php
namespace App\Http\QueryBuilder;
use App\Character;
use Illuminate\Support\Collection;
class CharacterSearchQueryBuilder extends SearchQueryBuilder
{
protected string $displayNameFieldName = "name";
/**
* @OA\Schema(
* schema="characters_search_query_orderby",
* description="Available Character order_by properties",
* type="string",
* enum={"mal_id", "name", "favorites"}
* )
*/
const ORDER_BY = [
'mal_id' => 'mal_id',
'name' => 'name',
'favorites' => 'member_favorites'
];
protected function getModelClass(): object|string
{
return Character::class;
}
protected function buildQuery(Collection $requestParameters, \Illuminate\Database\Eloquent\Builder|\Laravel\Scout\Builder $results): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder
{
return $results;
}
protected function getOrderByFieldMap(): array
{
return self::ORDER_BY;
}
public function getIdentifier(): string
{
return "character";
}
}

View File

@ -1,112 +0,0 @@
<?php
namespace App\Http\QueryBuilder;
use App\Club;
use App\Http\QueryBuilder\Traits\TypeResolver;
use Illuminate\Support\Collection;
class ClubSearchQueryBuilder extends SearchQueryBuilder
{
use TypeResolver;
protected string $displayNameFieldName = "title";
protected array $parameterNames = ["category", "type"];
/**
* @OA\Schema(
* schema="club_search_query_type",
* description="Club Search Query Type",
* type="string",
* enum={"public","private","secret"}
* )
*/
const MAP_TYPES = [
'public' => 'public',
'private' => 'private',
'secret' => 'secret'
];
/**
* @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"
* }
* )
*/
const MAP_CATEGORY = [
'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'
];
/**
* @OA\Schema(
* schema="club_search_query_orderby",
* description="Club Search Query OrderBy",
* type="string",
* enum={"mal_id","title","members_count","pictures_count","created"}
* )
*/
const ORDER_BY = [
'mal_id', 'title', 'members_count', 'pictures_count', 'created'
];
protected function getModelClass(): object|string
{
return Club::class;
}
protected function sanitizeParameters($parameters): Collection
{
$parameters = parent::sanitizeParameters($parameters);
$parameters["category"] = $this->mapCategory($parameters["category"]);
$parameters["type"] = $this->mapType($parameters["type"]);
return $parameters;
}
protected function buildQuery(Collection $requestParameters, \Illuminate\Database\Eloquent\Builder|\Laravel\Scout\Builder $results): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder
{
return $results;
}
protected function getOrderByFieldMap(): array
{
return self::ORDER_BY;
}
public function getIdentifier(): string
{
return "club";
}
protected function getTypeMap(): array
{
return self::MAP_TYPES;
}
/**
* @param string|null $category
* @return string|null
*/
private function mapCategory(?string $category = null) : ?string
{
$category = strtolower($category);
return self::MAP_CATEGORY[$category] ?? null;
}
}

View File

@ -1,128 +0,0 @@
<?php
namespace App\Http\QueryBuilder;
use App\Manga;
use Illuminate\Support\Collection;
class MangaSearchQueryBuilder extends MediaSearchQueryBuilder
{
protected array $parameterNames = ["magazine", "magazines", "rating"];
protected string $displayNameFieldName = "title";
/**
* @OA\Schema(
* schema="manga_search_query_type",
* description="Available Manga types",
* type="string",
* enum={"manga","novel", "lightnovel", "oneshot","doujin","manhwa","manhua"}
* )
*/
const MAP_TYPES = [
'manga' => 'Manga',
'novel' => 'Novel',
'lightnovel' => 'Light Novel',
'oneshot' => 'One-shot',
'doujin' => 'Doujinshi',
'manhwa' => 'Manhwa',
'manhua' => 'Manhua'
];
/**
* @OA\Schema(
* schema="manga_search_query_status",
* description="Available Manga statuses",
* type="string",
* enum={"publishing","complete","hiatus","discontinued","upcoming"}
* )
*/
const MAP_STATUS = [
'publishing' => 'Publishing',
'complete' => 'Finished',
'hiatus' => 'On Hiatus',
'discontinued' => 'Discontinued',
'upcoming' => 'Not yet published'
];
/**
* @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"}
* )
*/
const ORDER_BY = [
'chapters' => 'chapters',
'volumes' => 'volumes',
'start_date' => 'published.from',
'end_date' => 'published.to',
];
protected function buildQuery(Collection $requestParameters, \Illuminate\Database\Eloquent\Builder|\Laravel\Scout\Builder $results): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder
{
$builder = parent::buildQuery($requestParameters, $results);
$magazine = $requestParameters->get("magazine");
$magazines = $requestParameters->get("magazines");
if (!is_null($magazine)) $magazines = $magazine;
if (!is_null($magazines)) {
$magazines = explode(',', $magazines);
foreach ($magazines as $magazine) {
if (empty($magazine)) {
continue;
}
$magazine = (int)$magazine;
$builder = $builder
->orWhere('serializations.mal_id', $magazine);
}
}
return $builder;
}
protected function filterByStartDate(\Illuminate\Database\Eloquent\Builder|\Laravel\Scout\Builder $builder, string $startDate): \Illuminate\Database\Eloquent\Builder|\Laravel\Scout\Builder
{
return $builder->where('published.from', '>=', $startDate);
}
protected function filterByEndDate(\Illuminate\Database\Eloquent\Builder|\Laravel\Scout\Builder $builder, string $endDate): \Illuminate\Database\Eloquent\Builder|\Laravel\Scout\Builder
{
return $builder->where('published.to', '<=', $endDate);
}
protected function getStatusMap(): array
{
return self::MAP_STATUS;
}
protected function getTypeMap(): array
{
return self::MAP_TYPES;
}
protected function getAdultRating(): string
{
return "Doujinshi";
}
protected function getModelClass(): object|string
{
return Manga::class;
}
protected function getOrderByFieldMap(): array
{
$map = parent::getOrderByFieldMap();
return array_merge($map, self::ORDER_BY);
}
public function getIdentifier(): string
{
return "manga";
}
}

View File

@ -1,161 +0,0 @@
<?php
namespace App\Http\QueryBuilder;
use Illuminate\Support\Collection;
use App\Http\QueryBuilder\Traits\StatusResolver;
use App\Http\QueryBuilder\Traits\TypeResolver;
use App\IsoDateFormatter;
abstract class MediaSearchQueryBuilder extends SearchQueryBuilder
{
use IsoDateFormatter, StatusResolver, TypeResolver;
private array $mediaParameterNames = ["score", "sfw", "genres", "genres_exclude", "min_score", "max_score",
"start_date", "end_date", "status", "type"];
const ORDER_BY = [
'mal_id' => 'mal_id',
'title' => 'title',
'score' => 'score',
'scored_by' => 'scored_by',
'rank' => 'rank',
'popularity' => 'popularity',
'members' => 'members',
'favorites' => 'favorites'
];
protected function getParameterNames(): array
{
$parameterNames = parent::getParameterNames();
return array_merge($parameterNames, $this->mediaParameterNames);
}
protected function sanitizeParameters(Collection $parameters): Collection
{
$parameters = parent::sanitizeParameters($parameters);
if (!$parameters->has("score")) {
$parameters["score"] = 0;
}
$parameters["status"] = $this->mapStatus($parameters->get("status"));
$parameters["type"] = $this->mapType($parameters->get("type"));
return $parameters;
}
private function filterByGenre(\Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder $builder, int $genre, $exclude = false): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder
{
return $builder->where(function ($query) use ($genre, $exclude) {
return !$exclude ?
$query
->orWhere('genres.mal_id', $genre)
->orWhere('demographics.mal_id', $genre)
->orWhere('themes.mal_id', $genre)
->orWhere('explicit_genres.mal_id', $genre)
:
$query
->where('genres.mal_id', '!=', $genre)
->where('demographics.mal_id', '!=', $genre)
->where('themes.mal_id', '!=', $genre)
->where('explicit_genres.mal_id', '!=', $genre);
});
}
private function filterByGenres(\Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder $builder, string $genres, $exclude = false): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder
{
$genres = explode(',', $genres);
foreach ($genres as $genre) {
if (empty($genre)) {
continue;
}
$genre = (int) $genre;
$builder = $this->filterByGenre($builder, $genre, $exclude);
}
return $builder;
}
protected function buildQuery(Collection $requestParameters, \Illuminate\Database\Eloquent\Builder|\Laravel\Scout\Builder $results): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder
{
$builder = $results;
$start_date = $requestParameters->get("start_date");
$end_date = $requestParameters->get("end_date");
$score = $requestParameters->get("score");
$min_score = $requestParameters->get("min_score");
$max_score = $requestParameters->get("max_score");
$genres = $requestParameters->get("genres");
$genresExclude = $requestParameters->get("genres_exclude");
$sfw = $requestParameters->get("sfw");
$type = $requestParameters->get("type");
$status = $requestParameters->get("status");
if (!empty($start_date)) {
$builder = $this->filterByStartDate($builder, $this->formatIsoDateTime($start_date));
}
if (!empty($end_date)) {
$builder = $this->filterByEndDate($builder, $this->formatIsoDateTime($end_date));
}
if (!is_null($type)) {
$builder = $builder
->where('type', $type);
}
if (!is_null($status)) {
$builder = $builder
->where('status', $status);
}
if (!is_null($score)) {
$score = (float)$score;
$builder = $builder
->where('score', '>=', $score);
}
if (!is_null($min_score)) {
$min_score = (float)$min_score;
$builder = $builder
->where('score', '>=', $min_score);
}
if (!is_null($max_score)) {
$max_score = (float)$max_score;
$builder = $builder
->where('score', '<=', $max_score);
}
if (!is_null($genres)) {
$builder = $this->filterByGenres($builder, $genres);
}
if (!is_null($genresExclude)) {
$builder = $this->filterByGenres($builder, $genresExclude, true);
}
if (!is_null($sfw)) {
$builder = $builder
->where('rating', '!=', $this->getAdultRating());
}
return $builder;
}
protected function getOrderByFieldMap(): array
{
return self::ORDER_BY;
}
protected abstract function filterByStartDate(\Illuminate\Database\Eloquent\Builder|\Laravel\Scout\Builder $builder, string $startDate): \Illuminate\Database\Eloquent\Builder|\Laravel\Scout\Builder;
protected abstract function filterByEndDate(\Illuminate\Database\Eloquent\Builder|\Laravel\Scout\Builder $builder, string $endDate): \Illuminate\Database\Eloquent\Builder|\Laravel\Scout\Builder;
protected abstract function getAdultRating(): string;
}

View File

@ -1,46 +0,0 @@
<?php
namespace App\Http\QueryBuilder;
use App\Person;
use Illuminate\Support\Collection;
class PeopleSearchQueryBuilder extends SearchQueryBuilder
{
protected string $displayNameFieldName = "name";
/**
* @OA\Schema(
* schema="people_search_query_orderby",
* description="Available People order_by properties",
* type="string",
* enum={"mal_id", "name", "birthday", "favorites"}
* )
*/
const ORDER_BY = [
'mal_id' => 'mal_id',
'name' => 'name',
'birthday' => 'birthday',
'favorites' => 'member_favorites'
];
protected function getModelClass(): object|string
{
return Person::class;
}
protected function buildQuery(Collection $requestParameters, \Illuminate\Database\Eloquent\Builder|\Laravel\Scout\Builder $results): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder
{
return $results;
}
protected function getOrderByFieldMap(): array
{
return self::ORDER_BY;
}
public function getIdentifier(): string
{
return "people";
}
}

View File

@ -1,262 +0,0 @@
<?php
namespace App\Http\QueryBuilder;
use App\Http\QueryBuilder\Traits\PaginationParameterResolver;
use App\Services\ScoutSearchService;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use JetBrains\PhpStorm\ArrayShape;
use Laravel\Scout\Searchable;
use Jenssegers\Mongodb\Eloquent\Model;
abstract class SearchQueryBuilder implements SearchQueryBuilderService
{
use PaginationParameterResolver;
protected array $commonParameterNames = ["q", "order_by", "sort", "letter"];
protected array $parameterNames = [];
protected string $displayNameFieldName = "name";
protected bool $searchIndexesEnabled;
private ScoutSearchService $scoutSearchService;
private ?array $modelClassTraitsCache = null;
public function __construct(bool $searchIndexesEnabled, ScoutSearchService $scoutSearchService)
{
$this->searchIndexesEnabled = $searchIndexesEnabled;
$this->scoutSearchService = $scoutSearchService;
}
protected function getParametersFromRequest(Request $request): Collection
{
$paramNames = $this->getParameterNames();
$parameters = [];
foreach ($paramNames as $paramName) {
$parameters[$paramName] = $request->get($paramName);
}
if (!array_key_exists("q", $parameters)) {
$parameters["q"] = "";
}
return collect($parameters);
}
protected function getParameterNames(): array
{
return array_merge($this->commonParameterNames, $this->parameterNames);
}
protected function getSanitizedParametersFromRequest(Request $request): Collection
{
return $this->sanitizeParameters($this->getParametersFromRequest($request));
}
/**
* @throws \Exception
* @throws \Http\Client\Exception
*/
private function getQueryBuilder(Collection $requestParameters): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder
{
$modelClass = $this->getModelClass();
if (!in_array(Model::class, class_parents($modelClass))) {
throw new \Exception("Programming error: The getModelClass method should return a class which
inherits from \Jenssegers\Mongodb\Eloquent\Model.");
}
$q = $requestParameters->get("q");
if ($this->isSearchIndexUsed() && !empty($q)) {
$orderBy = $requestParameters->get("order_by");
$sort = $requestParameters->get("sort");
$searchOptions = [];
// todo: validate whether the specified field exists on the model
if (!empty($orderBy)) {
$searchOptions["order_by"] = $orderBy;
} else {
$searchOptions["order_by"] = null;
}
if (!empty($sort) && in_array($sort, ["asc", "desc"])) {
$searchOptions["sort_direction_descending"] = $sort == "desc";
} else {
$searchOptions["sort_direction_descending"] = false;
}
$builder = $this->scoutSearchService->search($modelClass, $q, $searchOptions["order_by"],
$searchOptions["sort_direction_descending"]);
} else {
// If "q" is not set, OR search indexes are disabled, we just get a query builder for the model.
// This way we can have a single place where we get the query builder from.
$builder = $modelClass::query();
}
return $builder;
}
private function buildQueryInternal(Collection $requestParameters, mixed $query): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder
{
$letter = $requestParameters->get('letter');
$q = $requestParameters->get('q');
$order_by = $requestParameters->get('order_by');
if (!is_null($letter)) {
$query = $query
->where($this->displayNameFieldName, 'like', "{$letter}%");
}
if (empty($q) && empty($order_by)) {
$query = $query
->orderBy('mal_id');
}
// if search index is disabled, use mongo's full text-search
if (!empty($q) && is_null($letter) && !$this->isSearchIndexUsed()) {
$query = $query
->whereRaw([
'$text' => [
'$search' => $q
],
], [
'textMatchScore' => [
'$meta' => 'textScore'
]
])
->orderBy('textMatchScore', 'desc');
}
// The ->filter() call is a local model scope function, which applies filters based on the query string
// parameters. This way we can simplify the code base and avoid a bunch of
// "if ($this->request->get("asd")) { }" lines in controllers.
$queryFilteredByQueryStringParams = $query->filter($requestParameters);
return $this->buildQuery($requestParameters, $queryFilteredByQueryStringParams);
}
public function isSearchIndexUsed(): bool
{
$modelClass = $this->getModelClass();
if (is_null($this->modelClassTraitsCache)) {
$this->modelClassTraitsCache = class_uses_recursive($modelClass);
}
$traits = $this->modelClassTraitsCache;
return in_array(Searchable::class, $traits) && $this->searchIndexesEnabled;
}
public function isScoutBuilder(mixed $results): bool
{
return $results instanceof \Laravel\Scout\Builder;
}
protected function sanitizeParameters(Collection $parameters): Collection
{
$parameters["sort"] = $this->mapSort($parameters->get("sort"));
$parameters["order_by"] = $this->mapOrderBy($parameters->get("order_by"));
return $parameters;
}
protected abstract function getModelClass(): object|string;
protected abstract function buildQuery(Collection $requestParameters, \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder $results): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder;
protected abstract function getOrderByFieldMap(): array;
/**
* @throws \Exception
* @throws \Http\Client\Exception
*/
public function query(Request $request): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder
{
$requestParameters = $this->getSanitizedParametersFromRequest($request);
$results = $this->getQueryBuilder($requestParameters);
// if search index is enabled, this way we only do the full-text search on the index, and filter further in mongodb.
// the $results variable can be a Builder from the Mongodb Eloquent or from Scout. Only Laravel\Scout\Builder
// has a query method which will result in a Mongodb Eloquent Builder.
return $this->isScoutBuilder($results) ? $results->query(function (\Illuminate\Database\Eloquent\Builder $query) use ($requestParameters) {
return $this->buildQueryInternal($requestParameters, $query);
}) : $this->buildQueryInternal($requestParameters, $results);
}
#[ArrayShape(['per_page' => "int", 'total' => "int", 'current_page' => "int", 'last_page' => "int", 'data' => "array"])]
public function paginate(Request $request, \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder $results): array
{
$paginated = $this->paginateBuilder($request, $results);
$items = $paginated->items();
foreach ($items as &$item) {
unset($item['_id']);
}
return [
'per_page' => $paginated->perPage(),
'total' => $paginated->total(),
'current_page' => $paginated->currentPage(),
'last_page' => $paginated->lastPage(),
'data' => $items
];
}
public function paginateBuilder(Request $request, \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder $results): LengthAwarePaginator
{
['limit' => $limit, 'page' => $page] = $this->getPaginateParameters($request);
if ($this->isSearchIndexUsed() && $this->isScoutBuilder($results)) {
/**
* @var \Laravel\Scout\Builder
*/
$scoutBuilder = $results;
// We want to influence the "getTotalCount" method of Scout's builder, so the pagination won't fail.
// In that method the "$limit" member variable is being check whether it's null or it has value.
// If it's set to a number then the result set will be limited which we do the pagination on.
// If it's set to null, then the pagination will be done on the whole result set.
/**
* @var LengthAwarePaginator $paginated
*/
$paginated = $scoutBuilder
->jikanPaginate(
$limit,
"page",
$page
);
} else {
$paginated = $results
->paginate(
$limit,
['*'],
null,
$page
);
}
return $paginated;
}
/**
* @param string|null $sort
* @return string|null
*/
public function mapSort(?string $sort = null): ?string
{
$sort = strtolower($sort);
return $sort === 'desc' ? 'desc' : 'asc';
}
/**
* @param string|null $orderBy
* @return string|null
*/
public function mapOrderBy(?string $orderBy): ?string
{
$orderBy = strtolower($orderBy);
$orderByFieldMap = collect($this->getOrderByFieldMap());
return $orderByFieldMap->get($orderBy, null);
}
}

View File

@ -1,12 +0,0 @@
<?php
namespace App\Http\QueryBuilder;
use Jenssegers\Mongodb\Eloquent\Builder;
use Illuminate\Http\Request;
interface SearchQueryBuilderInterface
{
static function query(Request $request, Builder $results) : Builder;
// static function paginate(Request $request, Builder $results);
}

View File

@ -1,18 +0,0 @@
<?php
namespace App\Http\QueryBuilder;
use Illuminate\Http\Request;
interface SearchQueryBuilderService
{
function query(Request $request): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder;
function paginate(Request $request, \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder $results): array;
function paginateBuilder(Request $request, \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder $results): \Illuminate\Contracts\Pagination\LengthAwarePaginator;
function getIdentifier(): string;
function isSearchIndexUsed(): bool;
}

View File

@ -1,89 +0,0 @@
<?php
namespace App\Http\QueryBuilder;
use App\Http\HttpHelper;
use App\Http\QueryBuilder\Traits\PaginationParameterResolver;
use Illuminate\Http\Request;
use Jenssegers\Mongodb\Eloquent\Builder;
use Jikan\Helper\Constants as JikanConstants;
use Jikan\Request\Search\UserSearchRequest;
class SearchQueryBuilderUsers
{
use PaginationParameterResolver;
/**
* @OA\Schema(
* schema="users_search_query_gender",
* description="Users Search Query Gender",
* type="string",
* enum={"any","male","female","nonbinary"}
* )
*/
private const MAP_GENDERS = [
'any' => JikanConstants::SEARCH_USER_GENDER_ANY,
'male' => JikanConstants::SEARCH_USER_GENDER_MALE,
'female' => JikanConstants::SEARCH_USER_GENDER_FEMALE,
'nonbinary' => JikanConstants::SEARCH_USER_GENDER_NONBINARY
];
public static function query(Request $request)
{
$page = $request->get('page') ?? 1;
$query = $request->get('q');
$gender = self::mapGender($request->get('gender'));
$location = $request->get('location');
$maxAge = $request->get('maxAge');
$minAge = $request->get('minAge');
return (new UserSearchRequest())
->setQuery($query)
->setGender($gender)
->setLocation($location)
->setMaxAge($maxAge)
->setMinAge($minAge)
->setPage($page);
}
public function paginate(Request $request, Builder $results)
{
["page" => $page, "limit" => $limit] = $this->getPaginateParameters($request);
$paginated = $results
->paginate(
$limit,
null,
null,
$page
);
$items = $paginated->items();
foreach ($items as &$item) {
unset($item['_id']);
}
return [
'per_page' => $paginated->perPage(),
'total' => $paginated->total(),
'current_page' => $paginated->currentPage(),
'last_page' => $paginated->lastPage(),
'data' => $items
];
}
public static function mapGender(?string $type = null) : ?int
{
if (!is_null($type)) {
return null;
}
$type = strtolower($type);
return self::MAP_GENDERS[$type] ?? null;
}
}

View File

@ -1,53 +0,0 @@
<?php
namespace App\Http\QueryBuilder;
use App\GenreAnime;
use App\GenreManga;
use App\Magazine;
use App\Producers;
use App\Services\ScoutSearchService;
use Illuminate\Support\Collection;
class SimpleSearchQueryBuilder extends SearchQueryBuilder
{
private string|object $modelClass;
private string $identifier;
private array $orderByFields;
const ORDER_BY = [
'mal_id', 'name', 'count'
];
public function __construct(string $identifier, string|object $modelClass, bool $searchIndexesEnabled,
ScoutSearchService $scoutSearchService, array $orderByFields = self::ORDER_BY)
{
if (!in_array($modelClass, [GenreAnime::class, GenreManga::class, Producers::class, Magazine::class])) {
throw new \InvalidArgumentException("Not supported model class has been provided.");
}
parent::__construct($searchIndexesEnabled, $scoutSearchService);
$this->modelClass = $modelClass;
$this->identifier = $identifier;
$this->orderByFields = $orderByFields;
}
protected function getModelClass(): object|string
{
return $this->modelClass;
}
protected function buildQuery(Collection $requestParameters, \Illuminate\Database\Eloquent\Builder|\Laravel\Scout\Builder $results): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder
{
return $results;
}
protected function getOrderByFieldMap(): array
{
return array_combine($this->orderByFields, $this->orderByFields);
}
public function getIdentifier(): string
{
return $this->identifier;
}
}

View File

@ -1,36 +0,0 @@
<?php
namespace App\Http\QueryBuilder;
use App\Http\QueryBuilder\Traits\TopMediaQueryParameterSanitizer;
use App\Http\QueryBuilder\Traits\TopQueryFilterResolver;
use App\Services\ScoutSearchService;
use Illuminate\Support\Collection;
class TopAnimeQueryBuilder extends AnimeSearchQueryBuilder
{
use TopQueryFilterResolver, TopMediaQueryParameterSanitizer;
protected array $parameterNames = ["producer", "producers", "rating", "filter"];
public function __construct(bool $searchIndexesEnabled, ScoutSearchService $scoutSearchService)
{
parent::__construct($searchIndexesEnabled, $scoutSearchService);
$this->filterMap = ["airing", "upcoming", "bypopularity", "favorite"];
}
protected function sanitizeParameters(Collection $parameters): Collection
{
return parent::sanitizeParameters($this->sanitizeTopMediaParameters($parameters));
}
protected function buildQuery(Collection $requestParameters, \Illuminate\Database\Eloquent\Builder|\Laravel\Scout\Builder $results): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder
{
$builder = parent::buildQuery($requestParameters, $results);
return $this->applyFilterParameter($requestParameters, $builder, false);
}
public function getIdentifier(): string
{
return "top_anime";
}
}

View File

@ -1,36 +0,0 @@
<?php
namespace App\Http\QueryBuilder;
use App\Http\QueryBuilder\Traits\TopMediaQueryParameterSanitizer;
use App\Http\QueryBuilder\Traits\TopQueryFilterResolver;
use App\Services\ScoutSearchService;
use Illuminate\Support\Collection;
class TopMangaQueryBuilder extends MangaSearchQueryBuilder
{
use TopQueryFilterResolver, TopMediaQueryParameterSanitizer;
protected array $parameterNames = ["magazine", "magazines", "rating", "filter"];
public function __construct(bool $searchIndexesEnabled, ScoutSearchService $scoutSearchService)
{
parent::__construct($searchIndexesEnabled, $scoutSearchService);
$this->filterMap = ["publishing", "upcoming", "bypopularity", "favorite"];
}
protected function sanitizeParameters(Collection $parameters): Collection
{
return parent::sanitizeParameters($this->sanitizeTopMediaParameters($parameters));
}
protected function buildQuery(Collection $requestParameters, \Illuminate\Database\Eloquent\Builder|\Laravel\Scout\Builder $results): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder
{
$builder = parent::buildQuery($requestParameters, $results);
return $this->applyFilterParameter($requestParameters, $builder, true);
}
public function getIdentifier(): string
{
return "top_manga";
}
}

View File

@ -1,133 +0,0 @@
<?php
namespace App\Http\QueryBuilder;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Jenssegers\Mongodb\Eloquent\Builder;
/**
* Class SearchQueryBuilderAnime
* @package App\Http\QueryBuilder
*/
class TopQueryBuilderAnime implements SearchQueryBuilderInterface
{
/**
*
*/
const MAP_TYPES = [
'tv' => 'TV',
'movie' => 'Movie',
'ova' => 'OVA',
'special' => 'Special',
'ona' => 'ONA',
'music' => 'Music',
];
/**
*
*/
const MAP_FILTER = [
'airing', 'upcoming', 'bypopularity', 'favorite'
];
/**
* @param string|null $type
* @return string|null
*/
public static function mapType(?string $type = null) : ?string
{
if (is_null($type)) {
return null;
}
$type = strtolower($type);
return self::MAP_TYPES[$type] ?? null;
}
/**
* @param Request $request
* @param Builder $builder
* @return Builder
*/
public static function query(Request $request, Builder $results) : Builder
{
$animeType = self::mapType($request->get('type'));
$filterType = self::mapFilter($request->get('filter'));
// MAL formula:
// Top All Anime sorted by rank
// Top Airing sorted by rank
// Top TV, Movie, OVA, ONA, Specials ordered by rank
// Top Upcoming ordered by members
// Most popular ordered by members
// Most favorites ordered by most favorites
$results = $results
->where('rating', '!=', 'Rx - Hentai');
if (!is_null($animeType)) {
$results = $results
->where('type', $animeType);
}
if (!is_null($filterType) && $filterType === 'airing') {
$results = $results
->where('airing', true)
->whereNotNull('rank')
->where('rank', '>', 0)
->orderBy('rank', 'asc');
return $results;
}
if (!is_null($filterType) && $filterType === 'upcoming') {
$results = $results
->where('status', 'Not yet aired')
->orderBy('members', 'desc');
return $results;
}
if (!is_null($filterType) && $filterType === 'bypopularity') {
$results = $results
->orderBy('members', 'desc');
return $results;
}
if (!is_null($filterType) && $filterType === 'favorite') {
$results = $results
->orderBy('favorites', 'desc');
return $results;
}
$results = $results
->whereNotNull('rank')
->where('rank', '>', 0)
->orderBy('rank', 'asc');
return $results;
}
/**
* @param string|null $filter
* @return string|null
*/
public static function mapFilter(?string $filter = null) : ?string
{
$filter = strtolower($filter);
if (!\in_array($filter, self::MAP_FILTER)) {
return null;
}
return $filter;
}
}

View File

@ -1,116 +0,0 @@
<?php
namespace App\Http\QueryBuilder;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Jenssegers\Mongodb\Eloquent\Builder;
/**
* Class SearchQueryBuilderAnime
* @package App\Http\QueryBuilder
*/
class TopQueryBuilderManga implements SearchQueryBuilderInterface
{
/**
*
*/
const MAP_TYPES = [
'manga' => 'Manga',
'novels' => 'Novel',
'lightnovels' => 'Light Novel',
'oneshots' => 'One-shot',
'doujin' => 'Doujinshi',
'manhwa' => 'Manhwa',
'manhua' => 'Manhua'
];
/**
*
*/
const MAP_FILTER = [
'publishing', 'upcoming', 'bypopularity', 'favorite'
];
/**
* @param Request $request
* @param Builder $builder
* @return Builder
*/
public static function query(Request $request, Builder $results) : Builder
{
$mangaType = self::mapType($request->get('type'));
$filterType = self::mapFilter($request->get('filter'));
$results = $results
->where('type', '!=', 'Doujinshi');
if (!is_null($mangaType)) {
$results = $results
->where('type', $mangaType);
}
if (!is_null($filterType) && $filterType === 'publishing') {
$results = $results
->where('publishing', true)
->whereNotNull('rank')
->where('rank', '>', 0)
->orderBy('rank', 'asc');
return $results;
}
if (!is_null($filterType) && $filterType === 'bypopularity') {
$results = $results
->orderBy('members', 'desc');
return $results;
}
if (!is_null($filterType) && $filterType === 'favorite') {
$results = $results
->orderBy('favorites', 'desc');
return $results;
}
$results = $results
->whereNotNull('rank')
->where('rank', '>', 0)
->orderBy('rank', 'asc');
return $results;
}
/**
* @param string|null $type
* @return string|null
*/
public static function mapType(?string $type = null) : ?string
{
if (is_null($type)) {
return null;
}
$type = strtolower($type);
return self::MAP_TYPES[$type] ?? null;
}
/**
* @param string|null $filter
* @return string|null
*/
public static function mapFilter(?string $filter = null) : ?string
{
$filter = strtolower($filter);
if (!\in_array($filter, self::MAP_FILTER)) {
return null;
}
return $filter;
}
}

View File

@ -1,37 +0,0 @@
<?php
namespace App\Http\QueryBuilder\Traits;
use Illuminate\Http\Request;
trait PaginationParameterResolver
{
private function getPaginateParameters(Request $request): array
{
$page = $request->get('page') ?? 1;
$default_max_results_per_page = env('MAX_RESULTS_PER_PAGE', 25);
$class_vars = get_class_vars(get_class($this));
// override on class basis
if (array_key_exists('MAX_RESULTS_PER_PAGE', $class_vars)) {
$default_max_results_per_page = $class_vars['MAX_RESULTS_PER_PAGE'];
}
$limit = $request->get('limit') ?? $default_max_results_per_page;
$limit = (int)$limit;
$page = (int)$page;
if ($limit <= 0) {
$limit = 1;
}
if ($limit > $default_max_results_per_page) {
$limit = $default_max_results_per_page;
}
if ($page <= 0) {
$page = 1;
}
return compact("page", "limit");
}
}

View File

@ -1,21 +0,0 @@
<?php
namespace App\Http\QueryBuilder\Traits;
trait StatusResolver
{
/**
* @param string|null $status
* @return string|null
*/
public function mapStatus(?string $status = null): ?string
{
$status = strtolower($status);
return $this->getStatusMap()[$status] ?? null;
}
protected function getStatusMap(): array
{
return [];
}
}

View File

@ -1,19 +0,0 @@
<?php
namespace App\Http\QueryBuilder\Traits;
use Illuminate\Support\Collection;
trait TopMediaQueryParameterSanitizer
{
protected function sanitizeTopMediaParameters(Collection $parameters): Collection
{
$unwanted_params = ["status", "q", "letter"];
foreach ($unwanted_params as $paramName) {
if ($parameters->offsetExists($paramName)) {
$parameters->offsetUnset($paramName);
}
}
return $parameters;
}
}

View File

@ -1,61 +0,0 @@
<?php
namespace App\Http\QueryBuilder\Traits;
use Illuminate\Support\Collection;
trait TopQueryFilterResolver
{
protected array $filterMap = [];
private function mapFilter(?string $filter = null) : ?string
{
$filter = strtolower($filter);
if (!\in_array($filter, $this->filterMap)) {
return null;
}
return $filter;
}
private function applyFilterParameter(Collection $requestParameters, \Illuminate\Database\Eloquent\Builder|\Laravel\Scout\Builder $builder, bool $is_manga): \Illuminate\Database\Eloquent\Builder|\Laravel\Scout\Builder
{
$filterType = $this->mapFilter($requestParameters->get("filter"));
$builder = $builder->where("rating", "!=", $this->getAdultRating());
$is_running = $is_manga ? "publishing" : "airing";
if ($filterType === "upcoming" && $is_manga) {
$filterType = "";
}
// MAL formula:
// Top All Anime sorted by rank
// Top Airing sorted by rank
// Top TV, Movie, OVA, ONA, Specials ordered by rank
// Top Upcoming ordered by members
// Most popular ordered by members
// Most favorites ordered by most favorites
return match ($filterType) {
$is_running => $builder
->where($is_running, true)
->whereNotNull("rank")
->where("rank", ">", 0)
->orderBy("rank", "asc"),
"upcoming" => $builder
->where('airing', true)
->whereNotNull('rank')
->where('rank', '>', 0)
->orderBy('rank', 'asc'),
"bypopularity" => $builder
->orderBy('members', 'desc'),
"favorite" => $builder
->orderBy('favorites', 'desc'),
default => $builder
->whereNotNull('rank')
->where('rank', '>', 0)
->orderBy('rank', 'asc'),
};
}
}

View File

@ -1,27 +0,0 @@
<?php
namespace App\Http\QueryBuilder\Traits;
trait TypeResolver
{
/**
* @param string|null $type
* @return string|null
*/
public function mapType(?string $type = null): ?string
{
if (empty($type)) {
return null;
}
$type = strtolower($type);
$typeMap = $this->getTypeMap();
// fallback to the original value, so we would show an empty result set.
return array_key_exists($type, $typeMap) ? $typeMap[$type] : $type;
}
protected function getTypeMap(): array
{
return [];
}
}

View File

@ -1,268 +0,0 @@
<?php
namespace App\Http\QueryBuilder;
use Jikan\Request\User\UserAnimeListRequest;
use Jikan\Request\User\UserMangaListRequest;
use Illuminate\Http\Request;
use Jikan\Helper\Constants as JikanConstants;
class UserListQueryBuilder
{
private const VALID_ANIME_ORDER_BY = [
'title' => JikanConstants::USER_ANIME_LIST_ORDER_BY_TITLE,
'finish_date' => JikanConstants::USER_ANIME_LIST_ORDER_BY_FINISHED_DATE,
'start_date' => JikanConstants::USER_ANIME_LIST_ORDER_BY_STARTED_DATE,
'finished_date' => JikanConstants::USER_ANIME_LIST_ORDER_BY_FINISHED_DATE, // Alias
'started_date' => JikanConstants::USER_ANIME_LIST_ORDER_BY_STARTED_DATE, // Alias
'score' => JikanConstants::USER_ANIME_LIST_ORDER_BY_SCORE,
'last_updated' => JikanConstants::USER_ANIME_LIST_ORDER_BY_LAST_UPDATED,
'type' => JikanConstants::USER_ANIME_LIST_ORDER_BY_TYPE,
'rated' => JikanConstants::USER_ANIME_LIST_ORDER_BY_RATED,
'rewatch' => JikanConstants::USER_ANIME_LIST_ORDER_BY_REWATCH_VALUE,
'rewatch_value' => JikanConstants::USER_ANIME_LIST_ORDER_BY_REWATCH_VALUE, // Alias
'priority' => JikanConstants::USER_ANIME_LIST_ORDER_BY_PRIORITY,
'progress' => JikanConstants::USER_ANIME_LIST_ORDER_BY_PROGRESS,
'episodes_watched' => JikanConstants::USER_ANIME_LIST_ORDER_BY_PROGRESS, // Alias
'storage' => JikanConstants::USER_ANIME_LIST_ORDER_BY_STORAGE,
'air_start' => JikanConstants::USER_ANIME_LIST_ORDER_BY_AIR_START,
'air_end' => JikanConstants::USER_ANIME_LIST_ORDER_BY_AIR_END,
'status' => JikanConstants::USER_ANIME_LIST_ORDER_BY_STATUS,
];
private const VALID_MANGA_ORDER_BY = [
'title' => JikanConstants::USER_MANGA_LIST_ORDER_BY_TITLE,
'finish_date' => JikanConstants::USER_MANGA_LIST_ORDER_BY_FINISHED_DATE,
'start_date' => JikanConstants::USER_MANGA_LIST_ORDER_BY_STARTED_DATE,
'finished_date' => JikanConstants::USER_MANGA_LIST_ORDER_BY_FINISHED_DATE,
'started_date' => JikanConstants::USER_MANGA_LIST_ORDER_BY_STARTED_DATE,
'score' => JikanConstants::USER_MANGA_LIST_ORDER_BY_SCORE,
'last_updated' => JikanConstants::USER_MANGA_LIST_ORDER_BY_LAST_UPDATED,
'priority' => JikanConstants::USER_MANGA_LIST_ORDER_BY_PRIORITY,
'progress' => JikanConstants::USER_MANGA_LIST_ORDER_BY_CHAPTERS,
'chapters_read' => JikanConstants::USER_MANGA_LIST_ORDER_BY_CHAPTERS,
'volumes_read' => JikanConstants::USER_MANGA_LIST_ORDER_BY_VOLUMES,
'type' => JikanConstants::USER_MANGA_LIST_ORDER_BY_TYPE,
'publish_start' => JikanConstants::USER_MANGA_LIST_ORDER_BY_PUBLISH_START,
'publish_end' => JikanConstants::USER_MANGA_LIST_ORDER_BY_PUBLISH_END,
'status' => JikanConstants::USER_MANGA_LIST_ORDER_BY_STATUS,
];
private const VALID_SORT = [
'ascending' => JikanConstants::USER_LIST_SORT_ASCENDING,
'asc' => JikanConstants::USER_LIST_SORT_ASCENDING,
'descending' => JikanConstants::USER_LIST_SORT_DESCENDING,
'desc' => JikanConstants::USER_LIST_SORT_DESCENDING,
];
private const VALID_SEASONS = [
'winter' => JikanConstants::WINTER,
'summer' => JikanConstants::SUMMER,
'fall' => JikanConstants::FALL,
'spring' => JikanConstants::SPRING,
];
private const VALID_AIRING_STATUS = [
'airing' => JikanConstants::USER_ANIME_LIST_CURRENTLY_AIRING,
'finished' => JikanConstants::USER_ANIME_LIST_FINISHED_AIRING,
'complete' => JikanConstants::USER_ANIME_LIST_FINISHED_AIRING,
'to_be_aired' => JikanConstants::USER_ANIME_LIST_NOT_YET_AIRED,
'not_yet_aired' => JikanConstants::USER_ANIME_LIST_NOT_YET_AIRED,
'tba' => JikanConstants::USER_ANIME_LIST_NOT_YET_AIRED,
'nya' => JikanConstants::USER_ANIME_LIST_NOT_YET_AIRED,
];
private const VALID_PUBLISHING_STATUS = [
'publishing' => JikanConstants::USER_MANGA_LIST_CURRENTLY_PUBLISHING,
'finished' => JikanConstants::USER_MANGA_LIST_FINISHED_PUBLISHING,
'complete' => JikanConstants::USER_MANGA_LIST_FINISHED_PUBLISHING,
'to_be_published' => JikanConstants::USER_MANGA_LIST_NOT_YET_PUBLISHED,
'not_yet_published' => JikanConstants::USER_MANGA_LIST_NOT_YET_PUBLISHED,
'tba' => JikanConstants::USER_MANGA_LIST_NOT_YET_PUBLISHED,
'nya' => JikanConstants::USER_MANGA_LIST_NOT_YET_PUBLISHED,
];
public static function create(Request $request, $parserRequest)
{
$query = $request->get('q');
$sort = $request->get('sort');
$orderBy = $request->get('order_by');
$orderBy2 = $request->get('order_by2');
$airedFrom = $request->get('aired_from');
$airedTo = $request->get('aired_to');
$producer = $request->get('producer');
$magazine = $request->get('magazine');
$season = $request->get('season');
$year = $request->get('year');
$airingStatus = $request->get('airing_status');
$publishedFrom = $request->get('published_from');
$publishedTo = $request->get('published_to');
$publishingStatus = $request->get('publishing_status');
// Search
if (!is_null($query)) {
$parserRequest->setTitle($query);
}
// Page
$parserRequest->setPage(
(int) $request->get('page') ?? 1
);
// Sort
$sort = $request->get('sort');
if (!is_null($sort)) {
if (array_key_exists($sort, self::VALID_SORT)) {
$sort = self::VALID_SORT[$sort];
}
}
if ($parserRequest instanceof UserAnimeListRequest) {
// Order By
if (!is_null($orderBy)) {
if (array_key_exists($orderBy, self::VALID_ANIME_ORDER_BY)) {
$orderBy = self::VALID_ANIME_ORDER_BY[$orderBy];
$parserRequest->setOrderBy($orderBy, $sort);
}
}
// Order By 2
if (!is_null($orderBy2)) {
if (array_key_exists($orderBy2, self::VALID_ANIME_ORDER_BY)) {
$orderBy2 = self::VALID_ANIME_ORDER_BY[$orderBy2];
$parserRequest->setOrderBy2($orderBy2, $sort);
}
}
// Aired From
if (!is_null($airedFrom)) {
if (preg_match("~[0-9]{4}-[0-9]{2}-[0-9]{2}~", $airedFrom)) {
$airedFrom = explode("-", $airedFrom);
$parserRequest->setAiredFrom(
(int) $airedFrom[2],
(int) $airedFrom[1],
(int) $airedFrom[0]
);
}
}
// Aired To
if (!is_null($airedTo)) {
if (preg_match("~[0-9]{4}-[0-9]{2}-[0-9]{2}~", $airedTo)) {
$airedTo = explode("-", $airedTo);
$parserRequest->setAiredTo(
(int) $airedTo[2],
(int) $airedTo[1],
(int) $airedTo[0]
);
}
}
// Producer
if (!is_null($producer)) {
$parserRequest->setProducer(
(int) $producer
);
}
// Season
if (!is_null($season)) {
if (\in_array($season, self::VALID_SEASONS)) {
$parserRequest->setSeason($season);
}
}
// Year
if (!is_null($year)) {
$parserRequest->setSeasonYear(
(int) $year
);
}
// Airing Status
if (!is_null($airingStatus)) {
if (array_key_exists($airingStatus, self::VALID_AIRING_STATUS)) {
$airingStatus = self::VALID_AIRING_STATUS[$airingStatus];
$parserRequest->setAiringStatus($airingStatus);
}
}
}
if ($parserRequest instanceof UserMangaListRequest) {
// Order By
if (!is_null($orderBy)) {
if (array_key_exists($orderBy, self::VALID_MANGA_ORDER_BY)) {
$orderBy = self::VALID_MANGA_ORDER_BY[$orderBy];
$parserRequest->setOrderBy($orderBy, $sort);
}
}
// Order By 2
if (!is_null($orderBy2)) {
if (array_key_exists($orderBy2, self::VALID_MANGA_ORDER_BY)) {
$orderBy2 = self::VALID_MANGA_ORDER_BY[$orderBy2];
$parserRequest->setOrderBy2($orderBy2, $sort);
}
}
// Published From
if (!is_null($publishedFrom)) {
if (preg_match("~[0-9]{4}-[0-9]{2}-[0-9]{2}~", $publishedFrom)) {
$publishedFrom = explode("-", $publishedFrom);
$parserRequest->setPublishedFrom(
(int) $publishedFrom[2],
(int) $publishedFrom[1],
(int) $publishedFrom[0]
);
}
}
// Published To
if (!is_null($publishedTo)) {
if (preg_match("~[0-9]{4}-[0-9]{2}-[0-9]{2}~", $publishedTo)) {
$publishedTo = explode("-", $publishedTo);
$parserRequest->setPublishedTo(
(int) $publishedTo[2],
(int) $publishedTo[1],
(int) $publishedTo[0]
);
}
}
// Magazine
if (!is_null($magazine)) {
$parserRequest->setMagazine(
(int) $magazine
);
}
// Publishing Status
if (!is_null($publishingStatus)) {
if (array_key_exists($publishingStatus, self::VALID_PUBLISHING_STATUS)) {
$publishingStatus = self::VALID_PUBLISHING_STATUS[$publishingStatus];
$parserRequest->setPublishingStatus($publishingStatus);
}
}
}
return $parserRequest;
}
}

View File

@ -275,7 +275,11 @@ class AppServiceProvider extends ServiceProvider
Features\UserRecommendationsLookupHandler::class => $unitOfWorkInstance->documents("users_recommendations"),
Features\UserClubsLookupHandler::class => $unitOfWorkInstance->documents("users_clubs"),
Features\UserExternalLookupHandler::class => $unitOfWorkInstance->users(),
Features\QueryRecentlyOnlineUsersHandler::class => $unitOfWorkInstance->documents("users_recently_online")
Features\QueryRecentlyOnlineUsersHandler::class => $unitOfWorkInstance->documents("users_recently_online"),
Features\QueryRecentlyAddedEpisodesHandler::class => $unitOfWorkInstance->documents("watch"),
Features\QueryPopularEpisodesHandler::class => $unitOfWorkInstance->documents("watch"),
Features\QueryRecentlyAddedPromoVideosHandler::class => $unitOfWorkInstance->documents("watch"),
Features\QueryPopularPromoVideosHandler::class => $unitOfWorkInstance->documents("watch")
];
foreach ($requestHandlersWithScraperService as $handlerClass => $repositoryInstance) {

View File

@ -39,7 +39,10 @@ final class DefaultCachedScraperService implements CachedScraperService
if ($results->isEmpty() || $results->isExpired()) {
$page = $page ?? 1;
// most of the time callback uses a call to the jikan lib, which scrapes some info from MAL
$data = $getMalDataCallback($this->jikan, $page);
$scraperResponse = $this->serializeScraperResult(Collection::unwrap($data));
$results = $this->updateCacheByKey($cacheKey, $results, $scraperResponse);
}

View File

@ -105,8 +105,6 @@ $app->routeMiddleware([
$app->configure('cache');
$app->configure('database');
$app->configure('queue');
$app->configure('controller-to-table-mapping');
$app->configure('controller');
$app->configure('roadrunner');
$app->configure('data');
$app->configure('jikan');

View File

@ -1,385 +0,0 @@
<?php
return [
/**
* Anime
*/
'AnimeController@main' => [
'table_name' => 'anime',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
'AnimeController@characters_staff' => [
'table_name' => 'anime_characters_staff',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
'AnimeController@characters' => [
'table_name' => 'anime_characters_staff',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
'AnimeController@staff' => [
'table_name' => 'anime_characters_staff',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
'AnimeController@episodes' => [
'table_name' => 'anime_episodes',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
'AnimeController@episode' => [
'table_name' => 'anime_episode',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
'AnimeController@news' => [
'table_name' => 'anime_news',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
'AnimeController@forum' => [
'table_name' => 'anime_forum',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
'AnimeController@videos' => [
'table_name' => 'anime_videos',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
'AnimeController@videosEpisodes' => [
'table_name' => 'anime_videos_episodes',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
'AnimeController@pictures' => [
'table_name' => 'anime_pictures',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
'AnimeController@stats' => [
'table_name' => 'anime_stats',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
'AnimeController@moreInfo' => [
'table_name' => 'anime_moreinfo',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
'AnimeController@recommendations' => [
'table_name' => 'anime_recommendations',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
'AnimeController@userupdates' => [
'table_name' => 'anime_userupdates',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
'AnimeController@reviews' => [
'table_name' => 'anime_reviews',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
/**
* Manga
*/
'MangaController@main' => [
'table_name' => 'manga',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
'MangaController@characters' => [
'table_name' => 'manga_characters',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
'MangaController@news' => [
'table_name' => 'manga_news',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
'MangaController@forum' => [
'table_name' => 'manga_news',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
'MangaController@pictures' => [
'table_name' => 'manga_pictures',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
'MangaController@stats' => [
'table_name' => 'manga_stats',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
'MangaController@moreInfo' => [
'table_name' => 'manga_moreinfo',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
'MangaController@recommendations' => [
'table_name' => 'manga_recommendations',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
'MangaController@userupdates' => [
'table_name' => 'manga_userupdates',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
'MangaController@reviews' => [
'table_name' => 'manga_reviews',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
/**
* Characters
*/
'CharacterController@main' => [
'table_name' => 'characters',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
'CharacterController@anime' => [
'table_name' => 'characters',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
'CharacterController@manga' => [
'table_name' => 'characters',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
'CharacterController@voices' => [
'table_name' => 'characters',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
'CharacterController@pictures' => [
'table_name' => 'characters_pictures',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
/**
* Person
*/
'PersonController@main' => [
'table_name' => 'people',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
'PersonController@anime' => [
'table_name' => 'people',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
'PersonController@manga' => [
'table_name' => 'people',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
'PersonController@seiyuu' => [
'table_name' => 'people',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
'PersonController@pictures' => [
'table_name' => 'people_pictures',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
/**
* Season
*/
'SeasonController@archive' => [
'table_name' => 'season_archive',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
'SeasonController@later' => [
'table_name' => 'season_later',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
'SeasonController@main' => [
'table_name' => 'season',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
/**
* Schedule
*/
'ScheduleController@main' => [
'table_name' => 'schedule',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
/**
* Producers
*/
'ProducerController@main' => [
'table_name' => 'producers',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
/**
* Magazines
*/
'MagazineController@main' => [
'table_name' => 'common',
'ttl' => env('CACHE_MAGAZINE_EXPIRE')
],
'MagazineController@resource' => [
'table_name' => 'common',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
/**
* Users
*/
'UserController@recentlyOnline' => [
'table_name' => 'users_recently_online',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
'UserController@profile' => [
'table_name' => 'users',
'ttl' => env('CACHE_USER_EXPIRE')
],
'UserController@statistics' => [
'table_name' => 'users',
'ttl' => env('CACHE_USER_EXPIRE')
],
'UserController@favorites' => [
'table_name' => 'users',
'ttl' => env('CACHE_USER_EXPIRE')
],
'UserController@about' => [
'table_name' => 'users',
'ttl' => env('CACHE_USER_EXPIRE')
],
'UserController@history' => [
'table_name' => 'users_history',
'ttl' => env('CACHE_USER_EXPIRE')
],
'UserController@friends' => [
'table_name' => 'users_friends',
'ttl' => env('CACHE_USER_EXPIRE')
],
'UserController@recommendations' => [
'table_name' => 'users_recommendations',
'ttl' => env('CACHE_USER_EXPIRE')
],
'UserController@reviews' => [
'table_name' => 'users_reviews',
'ttl' => env('CACHE_USER_EXPIRE')
],
'UserController@clubs' => [
'table_name' => 'users_clubs',
'ttl' => env('CACHE_USER_EXPIRE')
],
/**
* User Lists
*/
'UserController@animelist' => [
'table_name' => 'users_animelist',
'ttl' => env('CACHE_USERLIST_EXPIRE')
],
'UserController@mangalist' => [
'table_name' => 'users_mangalist',
'ttl' => env('CACHE_USERLIST_EXPIRE')
],
/**
* Genre
*/
'GenreController@mainAnime' => [
'table_name' => 'common',
'ttl' => env('CACHE_GENRE_EXPIRE')
],
'GenreController@mainManga' => [
'table_name' => 'common',
'ttl' => env('CACHE_GENRE_EXPIRE')
],
'GenreController@anime' => [
'table_name' => 'common',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
'GenreController@manga' => [
'table_name' => 'common',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
/**
* Top
*/
'TopController@anime' => [
'table_name' => 'common',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
'TopController@manga' => [
'table_name' => 'common',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
'TopController@characters' => [
'table_name' => 'common',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
'TopController@people' => [
'table_name' => 'common',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
'TopController@reviews' => [
'table_name' => 'common',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
/**
* Search
*/
'SearchController@anime' => [
'table_name' => 'common',
'ttl' => env('CACHE_SEARCH_EXPIRE')
],
'SearchController@manga' => [
'table_name' => 'common',
'ttl' => env('CACHE_SEARCH_EXPIRE')
],
'SearchController@character' => [
'table_name' => 'common',
'ttl' => env('CACHE_SEARCH_EXPIRE')
],
'SearchController@people' => [
'table_name' => 'common',
'ttl' => env('CACHE_SEARCH_EXPIRE')
],
'SearchController@users' => [
'table_name' => 'common',
'ttl' => env('CACHE_SEARCH_EXPIRE')
],
'SearchController@userById' => [
'table_name' => 'common',
'ttl' => env('CACHE_SEARCH_EXPIRE')
],
'SearchController@producers' => [
'table_name' => 'common',
'ttl' => env('CACHE_SEARCH_EXPIRE')
],
'ClubController@main' => [
'table_name' => 'clubs',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
'ClubController@members' => [
'table_name' => 'clubs_members',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
'ReviewsController@anime' => [
'table_name' => 'reviews',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
'ReviewsController@manga' => [
'table_name' => 'reviews',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
'RecommendationsController@anime' => [
'table_name' => 'recommendations',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
'RecommendationsController@manga' => [
'table_name' => 'recommendations',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
'WatchController@recentEpisodes' => [
'table_name' => 'watch',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
'WatchController@popularEpisodes' => [
'table_name' => 'watch',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
'WatchController@recentPromos' => [
'table_name' => 'watch',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
'WatchController@popularPromos' => [
'table_name' => 'watch',
'ttl' => env('CACHE_DEFAULT_EXPIRE')
],
];

View File

@ -2,8 +2,9 @@
namespace Database\Factories;
use App\Enums\AnimeStatusEnum;
use App\Enums\AnimeTypeEnum;
use App\GenreAnime;
use App\Http\QueryBuilder\AnimeSearchQueryBuilder;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\Factory;
@ -16,12 +17,12 @@ class AnimeModelFactoryDescriptor implements MediaModelFactoryDescriptor
public function typeParamMap(): array
{
return AnimeSearchQueryBuilder::MAP_TYPES;
return AnimeTypeEnum::toArray();
}
public function statusParamMap(): array
{
return AnimeSearchQueryBuilder::MAP_STATUS;
return AnimeStatusEnum::toArray();
}
public function hasRatingParam(): bool

View File

@ -2,8 +2,9 @@
namespace Database\Factories;
use App\Enums\MangaStatusEnum;
use App\Enums\MangaTypeEnum;
use App\GenreManga;
use App\Http\QueryBuilder\MangaSearchQueryBuilder;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\Factory;
@ -17,12 +18,12 @@ class MangaModelFactoryDescriptor implements MediaModelFactoryDescriptor
public function typeParamMap(): array
{
return MangaSearchQueryBuilder::MAP_TYPES;
return MangaTypeEnum::toArray();
}
public function statusParamMap(): array
{
return MangaSearchQueryBuilder::MAP_STATUS;
return MangaStatusEnum::toArray();
}
public function hasRatingParam(): bool

View File

@ -2,8 +2,7 @@
namespace Tests\Integration;
use App\Anime;
use App\CarbonDateRange;
use App\Http\QueryBuilder\AnimeSearchQueryBuilder;
use App\Http\QueryBuilder\MediaSearchQueryBuilder;
use App\Enums\AnimeOrderByEnum;
use App\Testing\ScoutFlush;
use App\Testing\SyntheticMongoDbTransaction;
use Illuminate\Database\Eloquent\Factories\Sequence;
@ -146,7 +145,7 @@ class AnimeSearchEndpointTest extends TestCase
public function orderByFieldMappingProvider(): array
{
$orderByFieldMappings = array_merge(MediaSearchQueryBuilder::ORDER_BY, AnimeSearchQueryBuilder::ORDER_BY);
$orderByFieldMappings = AnimeOrderByEnum::toArray();
$params = [];
foreach ($orderByFieldMappings as $paramName => $orderByField) {

View File

@ -3,8 +3,7 @@
namespace Tests\Integration;
use App\CarbonDateRange;
use App\Http\QueryBuilder\MangaSearchQueryBuilder;
use App\Http\QueryBuilder\MediaSearchQueryBuilder;
use App\Enums\MangaOrderByEnum;
use App\Manga;
use App\Testing\ScoutFlush;
use App\Testing\SyntheticMongoDbTransaction;
@ -133,7 +132,7 @@ class MangaSearchEndpointTest extends TestCase
public function orderByFieldMappingProvider(): array
{
$orderByFieldMappings = array_merge(MediaSearchQueryBuilder::ORDER_BY, MangaSearchQueryBuilder::ORDER_BY);
$orderByFieldMappings = MangaOrderByEnum::toArray();
$params = [];
foreach ($orderByFieldMappings as $paramName => $orderByField) {