mirror of
https://github.com/jikan-me/jikan-rest.git
synced 2025-02-20 11:23:35 +08:00
various fixes and refactorings
- genre filtering - added unapproved filtering - fixed sfw filtering - fixed kids filtering - fixed deprecation warnings as of php 8.1 - DateTime ctor can't take `null` anymore as first param - preg_replace doesn't accept `null` values as `$subject` - fixed failed items indexer (the --failed option of indexers) -- it didn't load the correct file making it impossible to retry the indexing - changed the document schema for search indexed anime/manga - added `approved` field to them - staging will require a reimport into TypeSense - the central filtering system will now process `sfw` and `unapproved` filters, so they will be applied implicitly through the `filter` model scope method.
This commit is contained in:
parent
61af2e0420
commit
6eff2af172
17
.env.dist
17
.env.dist
@ -99,7 +99,6 @@ QUEUE_DELAY_PER_JOB=5
|
|||||||
# Scout config
|
# Scout config
|
||||||
###
|
###
|
||||||
# For TypeSense use: typesense
|
# For TypeSense use: typesense
|
||||||
# For ElasticSearch use: Matchish\ScoutElasticSearch\Engines\ElasticSearchEngine
|
|
||||||
#SCOUT_DRIVER=typesense
|
#SCOUT_DRIVER=typesense
|
||||||
#SCOUT_QUEUE=false
|
#SCOUT_QUEUE=false
|
||||||
|
|
||||||
@ -112,22 +111,6 @@ QUEUE_DELAY_PER_JOB=5
|
|||||||
#TYPESENSE_SEARCH_EXHAUSTIVE=true
|
#TYPESENSE_SEARCH_EXHAUSTIVE=true
|
||||||
#TYPESENSE_SEARCH_CUTTOFF_MS=450
|
#TYPESENSE_SEARCH_CUTTOFF_MS=450
|
||||||
|
|
||||||
###
|
|
||||||
# ElasticSearch Config
|
|
||||||
###
|
|
||||||
# Host -- required
|
|
||||||
# ELASTICSEARCH_HOST=host:port
|
|
||||||
# you can use commas as sperator for additional nodes:
|
|
||||||
# ELASTICSEARCH_HOST=host:port,host:port
|
|
||||||
# username (optional)
|
|
||||||
# ELASTICSEARCH_USER=user
|
|
||||||
# password (optional)
|
|
||||||
# ELASTICSEARCH_PASSWORD=password
|
|
||||||
# api key (optional)
|
|
||||||
# ELASTICSEARCH_API_KEY=apikey
|
|
||||||
# cloud id (optional)
|
|
||||||
# ELASTICSEARCH_CLOUD_ID=cloudid
|
|
||||||
|
|
||||||
###
|
###
|
||||||
# GitHub generate report URL on fatal errors
|
# GitHub generate report URL on fatal errors
|
||||||
###
|
###
|
||||||
|
@ -11,6 +11,7 @@ use Carbon\CarbonImmutable;
|
|||||||
use Database\Factories\AnimeFactory;
|
use Database\Factories\AnimeFactory;
|
||||||
use Illuminate\Support\Facades\App;
|
use Illuminate\Support\Facades\App;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Jikan\Helper\Constants;
|
||||||
use Jikan\Jikan;
|
use Jikan\Jikan;
|
||||||
use Jikan\Request\Anime\AnimeRequest;
|
use Jikan\Request\Anime\AnimeRequest;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
@ -19,7 +20,10 @@ class Anime extends JikanApiSearchableModel
|
|||||||
{
|
{
|
||||||
use HasFactory, MediaFilters, FilteredByLetter;
|
use HasFactory, MediaFilters, FilteredByLetter;
|
||||||
|
|
||||||
protected array $filters = ["order_by", "status", "type", "sort", "max_score", "min_score", "score", "rating", "start_date", "end_date", "producer", "producers", "letter", "genres", "genres_exclude"];
|
protected array $filters = [
|
||||||
|
"order_by", "status", "type", "sort", "max_score", "min_score", "score", "rating", "start_date", "end_date",
|
||||||
|
"producer", "producers", "letter", "genres", "genres_exclude", "sfw", "unapproved", "kids"
|
||||||
|
];
|
||||||
/**
|
/**
|
||||||
* The attributes that are mass assignable.
|
* The attributes that are mass assignable.
|
||||||
*
|
*
|
||||||
@ -191,6 +195,29 @@ class Anime extends JikanApiSearchableModel
|
|||||||
return $query;
|
return $query;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @noinspection PhpUnused */
|
||||||
|
public function scopeExceptItemsWithAdultRating(\Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder $query): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder
|
||||||
|
{
|
||||||
|
return $query
|
||||||
|
->where("demographics.mal_id", "!=", Constants::GENRE_ANIME_HENTAI)
|
||||||
|
->where("demographics.mal_id", "!=", Constants::GENRE_ANIME_EROTICA)
|
||||||
|
->where("rating", "!=", AnimeRatingEnum::rx()->label);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @noinspection PhpUnused */
|
||||||
|
public function scopeExceptKidsItems(\Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder $query): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder
|
||||||
|
{
|
||||||
|
return $query
|
||||||
|
->where("demographics.mal_id", "!=", Constants::GENRE_ANIME_KIDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @noinspection PhpUnused */
|
||||||
|
public function scopeOnlyKidsItems(\Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder $query): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder
|
||||||
|
{
|
||||||
|
return $query
|
||||||
|
->where("demographics.mal_id", Constants::GENRE_ANIME_KIDS);
|
||||||
|
}
|
||||||
|
|
||||||
public static function scrape(int $id)
|
public static function scrape(int $id)
|
||||||
{
|
{
|
||||||
$data = app('JikanParser')->getAnime(new AnimeRequest($id));
|
$data = app('JikanParser')->getAnime(new AnimeRequest($id));
|
||||||
@ -237,6 +264,7 @@ class Anime extends JikanApiSearchableModel
|
|||||||
'synopsis' => $this->synopsis,
|
'synopsis' => $this->synopsis,
|
||||||
'season' => $this->season,
|
'season' => $this->season,
|
||||||
'year' => $this->year,
|
'year' => $this->year,
|
||||||
|
'approved' => $this->approved ?? false,
|
||||||
'producers' => $this->getMalIdsOfField($this->producers),
|
'producers' => $this->getMalIdsOfField($this->producers),
|
||||||
'studios' => $this->getMalIdsOfField($this->studios),
|
'studios' => $this->getMalIdsOfField($this->studios),
|
||||||
'licensors' => $this->getMalIdsOfField($this->licensors),
|
'licensors' => $this->getMalIdsOfField($this->licensors),
|
||||||
|
@ -45,10 +45,15 @@ trait MediaFilters
|
|||||||
|
|
||||||
foreach ($genres as $genreItem) {
|
foreach ($genres as $genreItem) {
|
||||||
$genre = (int) $genreItem;
|
$genre = (int) $genreItem;
|
||||||
$query = $query->orWhere('genres.mal_id', $genre)
|
// here we need a nested where clause
|
||||||
->orWhere('demographics.mal_id', $genre)
|
// this logically looks like: (genre = x OR demographic = x OR theme = x)
|
||||||
->orWhere('themes.mal_id', $genre)
|
// so: (any other where clauses from before) AND (genre = x OR demographic = x OR theme = x)
|
||||||
->orWhere('explicit_genres.mal_id', $genre);
|
$query = $query->where(function ($q) use ($genre) {
|
||||||
|
return $q->where('genres.mal_id', $genre)
|
||||||
|
->orWhere('demographics.mal_id', $genre)
|
||||||
|
->orWhere('themes.mal_id', $genre)
|
||||||
|
->orWhere('explicit_genres.mal_id', $genre);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return $query;
|
return $query;
|
||||||
@ -72,4 +77,26 @@ trait MediaFilters
|
|||||||
|
|
||||||
return $query;
|
return $query;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function filterBySfw(\Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder $query, bool $value): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder
|
||||||
|
{
|
||||||
|
// call scopeExceptItemsWithAdultRating method via $query->exceptItemsWithAdultRating()
|
||||||
|
/** @noinspection PhpUndefinedMethodInspection */
|
||||||
|
return $value ? $query->exceptItemsWithAdultRating() : $query;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function filterByUnapproved(\Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder $query, ?bool $value): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder
|
||||||
|
{
|
||||||
|
return !$value ? $query->where("approved", true) : $query;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function filterByKids(\Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder $query, ?bool $value): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder
|
||||||
|
{
|
||||||
|
if (is_null($value)) {
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
// call scopeOnlyKidsItems method via $query->onlyKidsItems()
|
||||||
|
/** @noinspection PhpUndefinedMethodInspection */
|
||||||
|
return $value ? $query->onlyKidsItems() : $query->exceptKidsItems();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,12 +37,11 @@ class AnimeScheduleIndexer extends Command
|
|||||||
/**
|
/**
|
||||||
* Execute the console command.
|
* Execute the console command.
|
||||||
*
|
*
|
||||||
* @return mixed
|
|
||||||
*/
|
*/
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
|
|
||||||
echo "Note: AnimeScheduleIndexer makes sure anime currently airing are upto update so the schedules endpoint returns fresh information\n\n";
|
echo "Note: AnimeScheduleIndexer makes sure anime currently airing are up to date so the schedules endpoint returns fresh information\n\n";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Schedule
|
* Schedule
|
||||||
@ -80,10 +79,6 @@ class AnimeScheduleIndexer extends Command
|
|||||||
sleep(3); // prevent rate-limit
|
sleep(3); // prevent rate-limit
|
||||||
|
|
||||||
echo "Updating {$i}/{$itemCount} \r";
|
echo "Updating {$i}/{$itemCount} \r";
|
||||||
try {
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
echo "[SKIPPED] Failed to fetch {$url}";
|
|
||||||
}
|
|
||||||
$i++;
|
$i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,8 +25,6 @@ use Spatie\LaravelData\Optional;
|
|||||||
*/
|
*/
|
||||||
final class AnimeSearchCommand extends MediaSearchCommand implements DataRequest
|
final class AnimeSearchCommand extends MediaSearchCommand implements DataRequest
|
||||||
{
|
{
|
||||||
use HasSfwParameter;
|
|
||||||
|
|
||||||
#[WithCast(EnumCast::class, AnimeStatusEnum::class), EnumValidation(AnimeStatusEnum::class)]
|
#[WithCast(EnumCast::class, AnimeStatusEnum::class), EnumValidation(AnimeStatusEnum::class)]
|
||||||
public AnimeStatusEnum|Optional $status;
|
public AnimeStatusEnum|Optional $status;
|
||||||
|
|
||||||
|
@ -22,5 +22,5 @@ trait HasKidsParameter
|
|||||||
use PreparesData;
|
use PreparesData;
|
||||||
|
|
||||||
#[BooleanType, WithCast(ContextualBooleanCast::class)]
|
#[BooleanType, WithCast(ContextualBooleanCast::class)]
|
||||||
public bool|Optional $kids = false;
|
public bool|Optional $kids;
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
namespace App\Dto;
|
namespace App\Dto;
|
||||||
|
|
||||||
use App\Dto\Concerns\HasSfwParameter;
|
use App\Dto\Concerns\HasSfwParameter;
|
||||||
|
use App\Dto\Concerns\HasUnapprovedParameter;
|
||||||
use Carbon\CarbonImmutable;
|
use Carbon\CarbonImmutable;
|
||||||
use Illuminate\Validation\Validator;
|
use Illuminate\Validation\Validator;
|
||||||
use Spatie\LaravelData\Attributes\MapInputName;
|
use Spatie\LaravelData\Attributes\MapInputName;
|
||||||
@ -23,7 +24,7 @@ use Spatie\LaravelData\Transformers\DateTimeInterfaceTransformer;
|
|||||||
|
|
||||||
class MediaSearchCommand extends SearchCommand
|
class MediaSearchCommand extends SearchCommand
|
||||||
{
|
{
|
||||||
use HasSfwParameter;
|
use HasSfwParameter, HasUnapprovedParameter;
|
||||||
|
|
||||||
#[MapInputName("min_score"), MapOutputName("min_score"), Between(0.00, 10.00), Numeric]
|
#[MapInputName("min_score"), MapOutputName("min_score"), Between(0.00, 10.00), Numeric]
|
||||||
public float|Optional $minScore;
|
public float|Optional $minScore;
|
||||||
|
@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
namespace App\Dto;
|
namespace App\Dto;
|
||||||
|
|
||||||
|
|
||||||
use App\Casts\ContextualBooleanCast;
|
|
||||||
use App\Casts\EnumCast;
|
use App\Casts\EnumCast;
|
||||||
use App\Concerns\HasRequestFingerprint;
|
use App\Concerns\HasRequestFingerprint;
|
||||||
use App\Contracts\DataRequest;
|
use App\Contracts\DataRequest;
|
||||||
@ -17,10 +15,8 @@ use App\Dto\Concerns\PreparesData;
|
|||||||
use App\Enums\AnimeScheduleFilterEnum;
|
use App\Enums\AnimeScheduleFilterEnum;
|
||||||
use App\Rules\Attributes\EnumValidation;
|
use App\Rules\Attributes\EnumValidation;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
use Spatie\LaravelData\Attributes\Validation\BooleanType;
|
|
||||||
use Spatie\LaravelData\Attributes\WithCast;
|
use Spatie\LaravelData\Attributes\WithCast;
|
||||||
use Spatie\LaravelData\Data;
|
use Spatie\LaravelData\Data;
|
||||||
use Spatie\LaravelData\Optional;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @implements DataRequest<JsonResponse>
|
* @implements DataRequest<JsonResponse>
|
||||||
|
@ -6,9 +6,7 @@ use App\Contracts\DataRequest;
|
|||||||
use App\Dto\Concerns\HasSfwParameter;
|
use App\Dto\Concerns\HasSfwParameter;
|
||||||
use App\Dto\Concerns\HasUnapprovedParameter;
|
use App\Dto\Concerns\HasUnapprovedParameter;
|
||||||
use App\Http\Resources\V4\MangaResource;
|
use App\Http\Resources\V4\MangaResource;
|
||||||
use Spatie\LaravelData\Attributes\Validation\BooleanType;
|
|
||||||
use Spatie\LaravelData\Data;
|
use Spatie\LaravelData\Data;
|
||||||
use Spatie\LaravelData\Optional;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @implements DataRequest<MangaResource>
|
* @implements DataRequest<MangaResource>
|
||||||
|
@ -17,7 +17,8 @@ final class MediaReviewsSortEnum extends Enum
|
|||||||
return [
|
return [
|
||||||
"mostVoted" => Constants::REVIEWS_SORT_MOST_VOTED,
|
"mostVoted" => Constants::REVIEWS_SORT_MOST_VOTED,
|
||||||
"newest" => Constants::REVIEWS_SORT_NEWEST,
|
"newest" => Constants::REVIEWS_SORT_NEWEST,
|
||||||
"oldest" => Constants::REVIEWS_SORT_OLDEST
|
"oldest" => Constants::REVIEWS_SORT_OLDEST,
|
||||||
|
"suggested" => "suggested",
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,8 +23,12 @@ final class QueryAnimeSchedulesHandler implements RequestHandler
|
|||||||
*/
|
*/
|
||||||
public function handle($request)
|
public function handle($request)
|
||||||
{
|
{
|
||||||
$limit = intval($request->limit ?? Env::get("MAX_RESULTS_PER_PAGE", 25));
|
$requestParams = collect($request->all());
|
||||||
$results = $this->repository->getCurrentlyAiring($request->dayFilter, $request->kids, $request->sfw, $request->unapproved);
|
$limit = $requestParams->get("limit");
|
||||||
|
$results = $this->repository->getCurrentlyAiring($request->dayFilter);
|
||||||
|
// apply sfw, kids and unapproved filters
|
||||||
|
/** @noinspection PhpUndefinedMethodInspection */
|
||||||
|
$results = $results->filter($requestParams);
|
||||||
$results = $results->paginate(
|
$results = $results->paginate(
|
||||||
$limit,
|
$limit,
|
||||||
["*"],
|
["*"],
|
||||||
|
@ -33,23 +33,9 @@ abstract class QueryAnimeSeasonHandlerBase implements RequestHandler
|
|||||||
$requestParams = collect($request->all());
|
$requestParams = collect($request->all());
|
||||||
$type = $requestParams->has("filter") ? $request->filter : null;
|
$type = $requestParams->has("filter") ? $request->filter : null;
|
||||||
$results = $this->getSeasonItems($request, $type);
|
$results = $this->getSeasonItems($request, $type);
|
||||||
|
// apply sfw, kids and unapproved filters
|
||||||
$includeUnapproved = $requestParams->get("unapproved", false);
|
/** @noinspection PhpUndefinedMethodInspection */
|
||||||
$includeKids = $requestParams->get("kids", false);
|
$results = $results->filter($requestParams);
|
||||||
$shouldBeSfw = $requestParams->get("sfw", false);
|
|
||||||
|
|
||||||
if (!$includeUnapproved) {
|
|
||||||
$results = $this->repository->excludeUnapprovedItems($results);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$includeKids) {
|
|
||||||
$results = $this->repository->excludeKidsItems($results);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($shouldBeSfw) {
|
|
||||||
$results = $this->repository->excludeNsfwItems($results);
|
|
||||||
}
|
|
||||||
|
|
||||||
$results = $results->paginate($request->limit, ["*"], null, $request->page);
|
$results = $results->paginate($request->limit, ["*"], null, $request->page);
|
||||||
|
|
||||||
$animeCollection = new AnimeCollection($results);
|
$animeCollection = new AnimeCollection($results);
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
namespace App\Features;
|
namespace App\Features;
|
||||||
|
|
||||||
use App\Contracts\AnimeRepository;
|
|
||||||
use App\Contracts\RequestHandler;
|
use App\Contracts\RequestHandler;
|
||||||
use App\Dto\QueryCurrentAnimeSeasonCommand;
|
use App\Dto\QueryCurrentAnimeSeasonCommand;
|
||||||
use App\Enums\AnimeSeasonEnum;
|
use App\Enums\AnimeSeasonEnum;
|
||||||
@ -28,7 +27,7 @@ final class QueryCurrentAnimeSeasonHandler extends QueryAnimeSeasonHandlerBase
|
|||||||
*/
|
*/
|
||||||
private function getCurrentSeason() : array
|
private function getCurrentSeason() : array
|
||||||
{
|
{
|
||||||
$date = new \DateTime(null, new \DateTimeZone('Asia/Tokyo'));
|
$date = new \DateTime('now', new \DateTimeZone('Asia/Tokyo'));
|
||||||
|
|
||||||
$year = (int) $date->format('Y');
|
$year = (int) $date->format('Y');
|
||||||
$month = (int) $date->format('n');
|
$month = (int) $date->format('n');
|
||||||
|
@ -2,47 +2,28 @@
|
|||||||
|
|
||||||
namespace App\Features;
|
namespace App\Features;
|
||||||
|
|
||||||
use App\Contracts\AnimeRepository;
|
use App\Anime;
|
||||||
use App\Contracts\RequestHandler;
|
use App\Contracts\RequestHandler;
|
||||||
use App\Dto\QueryRandomAnimeCommand;
|
use App\Dto\QueryRandomAnimeCommand;
|
||||||
use App\Http\Resources\V4\AnimeResource;
|
use App\Http\Resources\V4\AnimeResource;
|
||||||
use Illuminate\Support\Collection;
|
|
||||||
use Spatie\LaravelData\Optional;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @implements RequestHandler<QueryRandomAnimeCommand, AnimeResource>
|
* @implements RequestHandler<QueryRandomAnimeCommand, AnimeResource>
|
||||||
*/
|
*/
|
||||||
final class QueryRandomAnimeHandler implements RequestHandler
|
final class QueryRandomAnimeHandler implements RequestHandler
|
||||||
{
|
{
|
||||||
public function __construct(
|
|
||||||
private readonly AnimeRepository $repository
|
|
||||||
)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
public function handle($request): AnimeResource
|
public function handle($request): AnimeResource
|
||||||
{
|
{
|
||||||
$sfw = Optional::create() !== $request->sfw ? $request->sfw : null;
|
$queryable = Anime::query();
|
||||||
$unapproved = Optional::create() !== $request->unapproved ? $request->unapproved : null;
|
// apply sfw, kids and unapproved filters
|
||||||
|
/** @noinspection PhpUndefinedMethodInspection */
|
||||||
/**
|
$queryable = $queryable->filter(collect($request->all()));
|
||||||
* @var Collection $results;
|
|
||||||
*/
|
|
||||||
$results = $this->repository;
|
|
||||||
|
|
||||||
if (!$unapproved) {
|
|
||||||
$results->excludeUnapprovedItems($results);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($sfw) {
|
|
||||||
$results->excludeNsfwItems($results);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new AnimeResource(
|
return new AnimeResource(
|
||||||
$results->random()->first()
|
$queryable->random()->first()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,47 +2,28 @@
|
|||||||
|
|
||||||
namespace App\Features;
|
namespace App\Features;
|
||||||
|
|
||||||
use App\Contracts\MangaRepository;
|
|
||||||
use App\Contracts\RequestHandler;
|
use App\Contracts\RequestHandler;
|
||||||
use App\Dto\QueryRandomMangaCommand;
|
use App\Dto\QueryRandomMangaCommand;
|
||||||
use App\Http\Resources\V4\MangaResource;
|
use App\Http\Resources\V4\MangaResource;
|
||||||
use Illuminate\Support\Collection;
|
use App\Manga;
|
||||||
use Spatie\LaravelData\Optional;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @implements RequestHandler<QueryRandomMangaCommand, MangaResource>
|
* @implements RequestHandler<QueryRandomMangaCommand, MangaResource>
|
||||||
*/
|
*/
|
||||||
final class QueryRandomMangaHandler implements RequestHandler
|
final class QueryRandomMangaHandler implements RequestHandler
|
||||||
{
|
{
|
||||||
public function __construct(
|
|
||||||
private readonly MangaRepository $repository
|
|
||||||
)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
public function handle($request)
|
public function handle($request)
|
||||||
{
|
{
|
||||||
$sfw = Optional::create() !== $request->sfw ? $request->sfw : null;
|
$queryable = Manga::query();
|
||||||
$unapproved = Optional::create() !== $request->unapproved ? $request->unapproved : null;
|
// apply sfw, kids and unapproved filters
|
||||||
|
/** @noinspection PhpUndefinedMethodInspection */
|
||||||
/**
|
$queryable = $queryable->filter(collect($request->all()));
|
||||||
* @var Collection $results;
|
|
||||||
*/
|
|
||||||
$results = $this->repository;
|
|
||||||
|
|
||||||
if (!$unapproved) {
|
|
||||||
$results->excludeUnapprovedItems($results);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($sfw) {
|
|
||||||
$results->excludeNsfwItems($results);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new MangaResource(
|
return new MangaResource(
|
||||||
$results->random()->first()
|
$queryable->random()->first()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@ trait FilterQueryString
|
|||||||
};
|
};
|
||||||
|
|
||||||
$result = collect(array_filter($queryParameters->all(), $filter, ARRAY_FILTER_USE_KEY))
|
$result = collect(array_filter($queryParameters->all(), $filter, ARRAY_FILTER_USE_KEY))
|
||||||
->filter(fn ($v, $k) => !empty($v)) ?? Collection::empty();
|
->filter(fn ($v, $k) => $v !== "" && $v !== null && !(is_float($v) && is_nan($v))) ?? Collection::empty();
|
||||||
|
|
||||||
return $this->_normalizeOrderBy($result);
|
return $this->_normalizeOrderBy($result);
|
||||||
}
|
}
|
||||||
|
@ -83,6 +83,9 @@ abstract class JikanApiSearchableModel extends JikanApiModel implements Typesens
|
|||||||
|
|
||||||
protected function simplifyStringForSearch($val): string
|
protected function simplifyStringForSearch($val): string
|
||||||
{
|
{
|
||||||
|
if (!$val) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
return preg_replace("/[^[:alnum:][:space:]]/u", ' ', $val) ?? "";
|
return preg_replace("/[^[:alnum:][:space:]]/u", ' ', $val) ?? "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,10 +4,12 @@ namespace App;
|
|||||||
|
|
||||||
use App\Concerns\FilteredByLetter;
|
use App\Concerns\FilteredByLetter;
|
||||||
use App\Concerns\MediaFilters;
|
use App\Concerns\MediaFilters;
|
||||||
|
use App\Enums\MangaTypeEnum;
|
||||||
use App\Http\HttpHelper;
|
use App\Http\HttpHelper;
|
||||||
use Carbon\CarbonImmutable;
|
use Carbon\CarbonImmutable;
|
||||||
use Database\Factories\MangaFactory;
|
use Database\Factories\MangaFactory;
|
||||||
use Illuminate\Support\Facades\App;
|
use Illuminate\Support\Facades\App;
|
||||||
|
use Jikan\Helper\Constants;
|
||||||
use Jikan\Jikan;
|
use Jikan\Jikan;
|
||||||
use Jikan\Request\Manga\MangaRequest;
|
use Jikan\Request\Manga\MangaRequest;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
@ -16,7 +18,10 @@ class Manga extends JikanApiSearchableModel
|
|||||||
{
|
{
|
||||||
use HasFactory, MediaFilters, FilteredByLetter;
|
use HasFactory, MediaFilters, FilteredByLetter;
|
||||||
|
|
||||||
protected array $filters = ["order_by", "status", "type", "sort", "max_score", "min_score", "score", "start_date", "end_date", "magazine", "magazines", "letter", "genres", "genres_exclude"];
|
protected array $filters = [
|
||||||
|
"order_by", "status", "type", "sort", "max_score", "min_score", "score", "start_date", "end_date", "magazine",
|
||||||
|
"magazines", "letter", "genres", "genres_exclude", "sfw", "unapproved", "kids"
|
||||||
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The attributes that are mass assignable.
|
* The attributes that are mass assignable.
|
||||||
@ -28,7 +33,7 @@ class Manga extends JikanApiSearchableModel
|
|||||||
'images', 'status', 'type', 'volumes', 'chapters', 'publishing', 'published', 'rank', 'score',
|
'images', 'status', 'type', 'volumes', 'chapters', 'publishing', 'published', 'rank', 'score',
|
||||||
'scored_by', 'popularity', 'members', 'favorites', 'synopsis', 'background', 'related',
|
'scored_by', 'popularity', 'members', 'favorites', 'synopsis', 'background', 'related',
|
||||||
'genres', 'explicit_genres', 'themes', 'demographics', 'authors', 'serializations',
|
'genres', 'explicit_genres', 'themes', 'demographics', 'authors', 'serializations',
|
||||||
'createdAt', 'modifiedAt'
|
'createdAt', 'modifiedAt', 'approved'
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -102,6 +107,29 @@ class Manga extends JikanApiSearchableModel
|
|||||||
return $query;
|
return $query;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @noinspection PhpUnused */
|
||||||
|
public function scopeExceptItemsWithAdultRating(\Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder $query): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder
|
||||||
|
{
|
||||||
|
return $query
|
||||||
|
->where("type", "!=", MangaTypeEnum::doujin()->label)
|
||||||
|
->where("demographics.mal_id", "!=", Constants::GENRE_MANGA_HENTAI)
|
||||||
|
->where("demographics.mal_id", "!=", Constants::GENRE_MANGA_EROTICA);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @noinspection PhpUnused */
|
||||||
|
public function scopeExceptKidsItems(\Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder $query): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder
|
||||||
|
{
|
||||||
|
return $query
|
||||||
|
->where("demographics.mal_id", "!=", Constants::GENRE_MANGA_KIDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @noinspection PhpUnused */
|
||||||
|
public function scopeOnlyKidsItems(\Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder $query): \Laravel\Scout\Builder|\Illuminate\Database\Eloquent\Builder
|
||||||
|
{
|
||||||
|
return $query
|
||||||
|
->where("demographics.mal_id", Constants::GENRE_MANGA_KIDS);
|
||||||
|
}
|
||||||
|
|
||||||
public static function scrape(int $id)
|
public static function scrape(int $id)
|
||||||
{
|
{
|
||||||
$data = app('JikanParser')->getManga(new MangaRequest($id));
|
$data = app('JikanParser')->getManga(new MangaRequest($id));
|
||||||
@ -145,7 +173,7 @@ class Manga extends JikanApiSearchableModel
|
|||||||
'members' => $this->members,
|
'members' => $this->members,
|
||||||
'favorites' => $this->favorites,
|
'favorites' => $this->favorites,
|
||||||
'synopsis' => $this->synopsis,
|
'synopsis' => $this->synopsis,
|
||||||
'season' => $this->season,
|
'approved' => $this->approved ?? false,
|
||||||
'magazines' => $this->getMalIdsOfField($this->magazines),
|
'magazines' => $this->getMalIdsOfField($this->magazines),
|
||||||
'genres' => $this->getMalIdsOfField($this->genres),
|
'genres' => $this->getMalIdsOfField($this->genres),
|
||||||
'explicit_genres' => $this->getMalIdsOfField($this->explicit_genres)
|
'explicit_genres' => $this->getMalIdsOfField($this->explicit_genres)
|
||||||
|
@ -96,6 +96,7 @@ class AppServiceProvider extends ServiceProvider
|
|||||||
$scoutDriver = static::getSearchIndexDriver($this->app);
|
$scoutDriver = static::getSearchIndexDriver($this->app);
|
||||||
$serviceClass = match ($scoutDriver) {
|
$serviceClass = match ($scoutDriver) {
|
||||||
"typesense" => TypeSenseScoutSearchService::class,
|
"typesense" => TypeSenseScoutSearchService::class,
|
||||||
|
// experimental
|
||||||
"Matchish\ScoutElasticSearch\Engines\ElasticSearchEngine" => ElasticScoutSearchService::class,
|
"Matchish\ScoutElasticSearch\Engines\ElasticSearchEngine" => ElasticScoutSearchService::class,
|
||||||
default => DefaultScoutSearchService::class
|
default => DefaultScoutSearchService::class
|
||||||
};
|
};
|
||||||
@ -360,6 +361,7 @@ class AppServiceProvider extends ServiceProvider
|
|||||||
$services[] = Typesense::class;
|
$services[] = Typesense::class;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// experimental
|
||||||
if (Env::get("SCOUT_DRIVER") === "Matchish\ScoutElasticSearch\Engines\ElasticSearchEngine") {
|
if (Env::get("SCOUT_DRIVER") === "Matchish\ScoutElasticSearch\Engines\ElasticSearchEngine") {
|
||||||
$services[] = \Elastic\Elasticsearch\Client::class;
|
$services[] = \Elastic\Elasticsearch\Client::class;
|
||||||
}
|
}
|
||||||
|
@ -45,8 +45,7 @@ final class DefaultAnimeRepository extends DatabaseRepository implements AnimeRe
|
|||||||
|
|
||||||
public function exceptItemsWithAdultRating(): EloquentBuilder|ScoutBuilder
|
public function exceptItemsWithAdultRating(): EloquentBuilder|ScoutBuilder
|
||||||
{
|
{
|
||||||
$builder = $this->queryable()
|
$builder = $this->queryable();
|
||||||
->where("rating", "!=", AnimeRatingEnum::rx()->label);
|
|
||||||
|
|
||||||
$this->excludeNsfwItems($builder);
|
$this->excludeNsfwItems($builder);
|
||||||
return $builder;
|
return $builder;
|
||||||
@ -54,9 +53,7 @@ final class DefaultAnimeRepository extends DatabaseRepository implements AnimeRe
|
|||||||
|
|
||||||
public function excludeNsfwItems($builder): EloquentBuilder|ScoutBuilder
|
public function excludeNsfwItems($builder): EloquentBuilder|ScoutBuilder
|
||||||
{
|
{
|
||||||
return $builder
|
return $builder->exceptItemsWithAdultRating();
|
||||||
->where("demographics.mal_id", "!=", Constants::GENRE_ANIME_HENTAI)
|
|
||||||
->where("demographics.mal_id", "!=", Constants::GENRE_ANIME_EROTICA);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function excludeUnapprovedItems($builder): Collection|EloquentBuilder|ScoutBuilder
|
public function excludeUnapprovedItems($builder): Collection|EloquentBuilder|ScoutBuilder
|
||||||
@ -67,8 +64,7 @@ final class DefaultAnimeRepository extends DatabaseRepository implements AnimeRe
|
|||||||
|
|
||||||
public function excludeKidsItems($builder): EloquentBuilder|ScoutBuilder
|
public function excludeKidsItems($builder): EloquentBuilder|ScoutBuilder
|
||||||
{
|
{
|
||||||
return $builder
|
return $builder->exceptKidsItems();
|
||||||
->where("demographics.mal_id", "!=", Constants::GENRE_ANIME_KIDS);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function orderByPopularity(): EloquentBuilder|ScoutBuilder
|
public function orderByPopularity(): EloquentBuilder|ScoutBuilder
|
||||||
@ -121,9 +117,17 @@ final class DefaultAnimeRepository extends DatabaseRepository implements AnimeRe
|
|||||||
?AnimeTypeEnum $type = null
|
?AnimeTypeEnum $type = null
|
||||||
): EloquentBuilder
|
): EloquentBuilder
|
||||||
{
|
{
|
||||||
$queryable = $this->queryable(true)->whereBetween("aired.from", [
|
// $queryable = $this->queryable(true)->whereBetween("aired.from", [
|
||||||
$from->toAtomString(),
|
// $from->toAtomString(),
|
||||||
$to->modify("last day of this month")->toAtomString()
|
// $to->modify("last day of this month")->toAtomString()
|
||||||
|
// ]);
|
||||||
|
|
||||||
|
/** @noinspection PhpParamsInspection */
|
||||||
|
$queryable = $this->queryable(true)->whereRaw([
|
||||||
|
"aired.from" => [
|
||||||
|
'$gte' => $from->toAtomString(),
|
||||||
|
'$lte' => $to->modify("last day of this month")->toAtomString()
|
||||||
|
]
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (!is_null($type)) {
|
if (!is_null($type)) {
|
||||||
|
@ -50,9 +50,7 @@ final class DefaultMangaRepository extends DatabaseRepository implements MangaRe
|
|||||||
|
|
||||||
public function exceptItemsWithAdultRating(): EloquentBuilder|ScoutBuilder
|
public function exceptItemsWithAdultRating(): EloquentBuilder|ScoutBuilder
|
||||||
{
|
{
|
||||||
return $this->queryable()
|
/** @noinspection PhpUndefinedMethodInspection */
|
||||||
->where("type", "!=", MangaTypeEnum::doujin()->label)
|
return $this->queryable()->exceptItemsWithAdultRating();
|
||||||
->where("demographics.mal_id", "!=", Constants::GENRE_MANGA_HENTAI)
|
|
||||||
->where("demographics.mal_id", "!=", Constants::GENRE_MANGA_EROTICA);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -143,11 +143,12 @@ $app->instance('JikanParser', $jikan);
|
|||||||
$app->instance('SerializerV4', SerializerFactory::createV4());
|
$app->instance('SerializerV4', SerializerFactory::createV4());
|
||||||
$app->register(Laravel\Scout\ScoutServiceProvider::class);
|
$app->register(Laravel\Scout\ScoutServiceProvider::class);
|
||||||
|
|
||||||
// we support TypeSense and ElasticSearch as search indexes.
|
// we support TypeSense search index.
|
||||||
if (env("SCOUT_DRIVER") === "typesense") {
|
if (env("SCOUT_DRIVER") === "typesense") {
|
||||||
// in this case the TYPESENSE_HOST env var should be set too
|
// in this case the TYPESENSE_HOST env var should be set too
|
||||||
$app->register(\Typesense\LaravelTypesense\TypesenseServiceProvider::class);
|
$app->register(\Typesense\LaravelTypesense\TypesenseServiceProvider::class);
|
||||||
}
|
}
|
||||||
|
// experimental support for ElasticSearch search index
|
||||||
if (env("SCOUT_DRIVER") === "Matchish\ScoutElasticSearch\Engines\ElasticSearchEngine") {
|
if (env("SCOUT_DRIVER") === "Matchish\ScoutElasticSearch\Engines\ElasticSearchEngine") {
|
||||||
// in this case the ELASTICSEARCH_HOST env var should be set too
|
// in this case the ELASTICSEARCH_HOST env var should be set too
|
||||||
$app->register(\Matchish\ScoutElasticSearch\ElasticSearchServiceProvider::class);
|
$app->register(\Matchish\ScoutElasticSearch\ElasticSearchServiceProvider::class);
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
"fabpot/goutte": "^4.0",
|
"fabpot/goutte": "^4.0",
|
||||||
"flipbox/lumen-generator": "^9.0",
|
"flipbox/lumen-generator": "^9.0",
|
||||||
"illuminate/redis": "^9.0",
|
"illuminate/redis": "^9.0",
|
||||||
"jenssegers/mongodb": "^3.8",
|
"jenssegers/mongodb": "^3.9",
|
||||||
"jikan-me/jikan": "^4",
|
"jikan-me/jikan": "^4",
|
||||||
"jms/serializer": "^3.0",
|
"jms/serializer": "^3.0",
|
||||||
"laravel/legacy-factories": "^1.1",
|
"laravel/legacy-factories": "^1.1",
|
||||||
|
14
composer.lock
generated
14
composer.lock
generated
@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "8aae72ff5c407ed2db0de3d15ef88c6d",
|
"content-hash": "bd75cf3fe6348185584fa3cbde3754e7",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "amphp/amp",
|
"name": "amphp/amp",
|
||||||
@ -4286,16 +4286,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "jenssegers/mongodb",
|
"name": "jenssegers/mongodb",
|
||||||
"version": "v3.9.4",
|
"version": "v3.9.5",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/jenssegers/laravel-mongodb.git",
|
"url": "https://github.com/jenssegers/laravel-mongodb.git",
|
||||||
"reference": "0b03010682e5041ea80177a3db2d6509da92a9a5"
|
"reference": "6ce35ace85a5946f943d7f493f93aebb9a6d129d"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/jenssegers/laravel-mongodb/zipball/0b03010682e5041ea80177a3db2d6509da92a9a5",
|
"url": "https://api.github.com/repos/jenssegers/laravel-mongodb/zipball/6ce35ace85a5946f943d7f493f93aebb9a6d129d",
|
||||||
"reference": "0b03010682e5041ea80177a3db2d6509da92a9a5",
|
"reference": "6ce35ace85a5946f943d7f493f93aebb9a6d129d",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@ -4352,7 +4352,7 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/jenssegers/laravel-mongodb/issues",
|
"issues": "https://github.com/jenssegers/laravel-mongodb/issues",
|
||||||
"source": "https://github.com/jenssegers/laravel-mongodb/tree/v3.9.4"
|
"source": "https://github.com/jenssegers/laravel-mongodb/tree/v3.9.5"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@ -4364,7 +4364,7 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2023-02-10T13:18:35+00:00"
|
"time": "2023-02-16T12:20:36+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "jetbrains/phpstorm-stubs",
|
"name": "jetbrains/phpstorm-stubs",
|
||||||
|
@ -66,6 +66,7 @@ class AnimeFactory extends JikanMediaModelFactory
|
|||||||
"members" => $this->faker->randomDigitNotnull(),
|
"members" => $this->faker->randomDigitNotnull(),
|
||||||
"favorites" => $this->faker->randomDigitNotnull(),
|
"favorites" => $this->faker->randomDigitNotnull(),
|
||||||
"synopsis" => "test",
|
"synopsis" => "test",
|
||||||
|
"approved" => true,
|
||||||
"background" => "test",
|
"background" => "test",
|
||||||
"premiered" => $this->faker->randomElement(["Winter", "Spring", "Fall", "Summer"]),
|
"premiered" => $this->faker->randomElement(["Winter", "Spring", "Fall", "Summer"]),
|
||||||
"broadcast" => [
|
"broadcast" => [
|
||||||
|
@ -57,6 +57,7 @@ class MangaFactory extends JikanMediaModelFactory
|
|||||||
"members" => $this->faker->randomDigitNotNull(),
|
"members" => $this->faker->randomDigitNotNull(),
|
||||||
"favorites" => $this->faker->randomDigitNotNull(),
|
"favorites" => $this->faker->randomDigitNotNull(),
|
||||||
"synopsis" => "test",
|
"synopsis" => "test",
|
||||||
|
"approved" => true,
|
||||||
"background" => "test",
|
"background" => "test",
|
||||||
"authors" => [
|
"authors" => [
|
||||||
[
|
[
|
||||||
|
Loading…
x
Reference in New Issue
Block a user