mirror of
https://github.com/jikan-me/jikan-rest.git
synced 2025-02-20 11:23:35 +08:00
wip - refactor finished
This commit is contained in:
parent
4a25c30d7d
commit
1e302fb62c
17
app/Dto/QueryPopularEpisodesCommand.php
Normal file
17
app/Dto/QueryPopularEpisodesCommand.php
Normal 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;
|
||||
}
|
17
app/Dto/QueryPopularPromoVideosCommand.php
Normal file
17
app/Dto/QueryPopularPromoVideosCommand.php
Normal 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;
|
||||
}
|
17
app/Dto/QueryRecentlyAddedEpisodesCommand.php
Normal file
17
app/Dto/QueryRecentlyAddedEpisodesCommand.php
Normal 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;
|
||||
}
|
18
app/Dto/QueryRecentlyAddedPromoVideosCommand.php
Normal file
18
app/Dto/QueryRecentlyAddedPromoVideosCommand.php
Normal 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;
|
||||
}
|
@ -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()));
|
||||
|
||||
|
29
app/Features/QueryPopularEpisodesHandler.php
Normal file
29
app/Features/QueryPopularEpisodesHandler.php
Normal 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())
|
||||
);
|
||||
}
|
||||
}
|
29
app/Features/QueryPopularPromoVideosHandler.php
Normal file
29
app/Features/QueryPopularPromoVideosHandler.php
Normal 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())
|
||||
);
|
||||
}
|
||||
}
|
29
app/Features/QueryRecentlyAddedEpisodesHandler.php
Normal file
29
app/Features/QueryRecentlyAddedEpisodesHandler.php
Normal 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())
|
||||
);
|
||||
}
|
||||
}
|
30
app/Features/QueryRecentlyAddedPromoVideosHandler.php
Normal file
30
app/Features/QueryRecentlyAddedPromoVideosHandler.php
Normal 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)
|
||||
);
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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";
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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";
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -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";
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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";
|
||||
}
|
||||
}
|
@ -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";
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
}
|
@ -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 [];
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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'),
|
||||
};
|
||||
}
|
||||
}
|
@ -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 [];
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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');
|
||||
|
@ -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')
|
||||
],
|
||||
|
||||
];
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user