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:
pushrbx 2023-05-01 16:52:52 +01:00
parent 61af2e0420
commit 6eff2af172
26 changed files with 153 additions and 137 deletions

View File

@ -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
### ###

View File

@ -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),

View File

@ -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
// this logically looks like: (genre = x OR demographic = x OR theme = x)
// so: (any other where clauses from before) AND (genre = x OR demographic = x OR theme = x)
$query = $query->where(function ($q) use ($genre) {
return $q->where('genres.mal_id', $genre)
->orWhere('demographics.mal_id', $genre) ->orWhere('demographics.mal_id', $genre)
->orWhere('themes.mal_id', $genre) ->orWhere('themes.mal_id', $genre)
->orWhere('explicit_genres.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();
}
} }

View File

@ -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++;
} }

View File

@ -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;

View File

@ -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;
} }

View File

@ -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;

View File

@ -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>

View File

@ -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>

View File

@ -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",
]; ];
} }
} }

View File

@ -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,
["*"], ["*"],

View File

@ -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);

View File

@ -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');

View File

@ -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()
); );
} }

View File

@ -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()
); );
} }

View File

@ -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);
} }

View File

@ -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) ?? "";
} }
} }

View File

@ -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)

View File

@ -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;
} }

View File

@ -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)) {

View File

@ -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);
} }
} }

View File

@ -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);

View File

@ -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
View File

@ -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",

View File

@ -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" => [

View File

@ -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" => [
[ [