From 0211fc4128d385a6a9d906dabf50cc5b8a150bf3 Mon Sep 17 00:00:00 2001 From: pushrbx Date: Mon, 30 Jan 2023 20:41:39 +0000 Subject: [PATCH] multiple fixes - updated api docs - fixed top reviews endpoint - fixed reviews parsing - added "contextual" boolean query string parameters, so params without value can be interpreted as "boolean". E.g. ?sfw or ?kid in the url would add "true" value to their corresponding field in the DTO - fixed typesense issues --- app/Anime.php | 2 +- app/Casts/ContextualBooleanCast.php | 25 + app/Casts/EnumCast.php | 2 +- app/Character.php | 2 +- app/Club.php | 2 +- app/Dto/AnimeReviewsLookupCommand.php | 8 +- app/Dto/Concerns/HasLimitParameter.php | 2 +- app/Dto/Concerns/HasSfwParameter.php | 16 + .../Concerns/MapsDefaultLimitParameter.php | 21 - app/Dto/Concerns/PreparesData.php | 51 ++ app/Dto/MangaReviewsLookupCommand.php | 8 +- app/Dto/MediaSearchCommand.php | 5 +- app/Dto/QueryAnimeSchedulesCommand.php | 10 +- app/Dto/QueryRandomAnimeCommand.php | 6 +- app/Dto/QueryRandomMangaCommand.php | 4 +- app/Dto/QueryReviewsCommand.php | 8 +- app/Dto/QueryTopReviewsCommand.php | 19 +- app/Enums/AnimeOrderByEnum.php | 2 +- app/Enums/GenderEnum.php | 9 +- app/Enums/TopReviewsTypeEnum.php | 20 + app/Features/QueryTopReviewsHandler.php | 8 +- app/GenreAnime.php | 2 +- app/GenreManga.php | 2 +- .../Controllers/V4DB/RandomController.php | 23 +- app/Http/Controllers/V4DB/TopController.php | 27 +- app/Http/HttpHelper.php | 22 +- app/Http/Resources/V4/ReviewsResource.php | 8 +- app/JikanApiModel.php | 2 +- app/Magazine.php | 2 +- app/Manga.php | 2 +- app/Person.php | 2 +- app/Producers.php | 9 +- app/Profile.php | 4 +- app/Support/helpers.php | 15 + storage/api-docs/api-docs.json | 589 ++++++++++-------- 35 files changed, 571 insertions(+), 368 deletions(-) create mode 100644 app/Casts/ContextualBooleanCast.php create mode 100644 app/Dto/Concerns/HasSfwParameter.php delete mode 100644 app/Dto/Concerns/MapsDefaultLimitParameter.php create mode 100644 app/Dto/Concerns/PreparesData.php create mode 100644 app/Enums/TopReviewsTypeEnum.php diff --git a/app/Anime.php b/app/Anime.php index a3d369c..7034aea 100644 --- a/app/Anime.php +++ b/app/Anime.php @@ -211,7 +211,7 @@ class Anime extends JikanApiSearchableModel { return [ 'id' => (string) $this->mal_id, - 'mal_id' => (string) $this->mal_id, + 'mal_id' => (int) $this->mal_id, 'start_date' => $this->convertToTimestamp($this->aired['from']), 'end_date' => $this->convertToTimestamp($this->aired['to']), 'title' => $this->title, diff --git a/app/Casts/ContextualBooleanCast.php b/app/Casts/ContextualBooleanCast.php new file mode 100644 index 0000000..c5dd6a6 --- /dev/null +++ b/app/Casts/ContextualBooleanCast.php @@ -0,0 +1,25 @@ +name; + + if (array_key_exists($propertyName, $context) && $context[$propertyName] === "") + { + return true; + } + + return $value; + } +} diff --git a/app/Casts/EnumCast.php b/app/Casts/EnumCast.php index 2bbeb55..36b42ce 100644 --- a/app/Casts/EnumCast.php +++ b/app/Casts/EnumCast.php @@ -9,7 +9,7 @@ use Spatie\LaravelData\Exceptions\CannotCastEnum; use Spatie\LaravelData\Support\DataProperty; use Throwable; -class EnumCast implements Cast +final class EnumCast implements Cast { public function __construct( protected ?string $type = null diff --git a/app/Character.php b/app/Character.php index 143cbc9..0b7d189 100644 --- a/app/Character.php +++ b/app/Character.php @@ -71,7 +71,7 @@ class Character extends JikanApiSearchableModel { return [ 'id' => (string) $this->mal_id, - 'mal_id' => (string) $this->mal_id, + 'mal_id' => (int) $this->mal_id, 'name' => $this->name, 'name_kanji' => $this->name_kanji, 'member_favorites' => $this->member_favorites diff --git a/app/Club.php b/app/Club.php index d354f26..dfbc388 100644 --- a/app/Club.php +++ b/app/Club.php @@ -78,7 +78,7 @@ class Club extends JikanApiSearchableModel { return [ 'id' => (string) $this->mal_id, - 'mal_id' => (string) $this->mal_id, + 'mal_id' => (int) $this->mal_id, 'name' => $this->name, 'category' => $this->category, 'created' => $this->convertToTimestamp($this->created), diff --git a/app/Dto/AnimeReviewsLookupCommand.php b/app/Dto/AnimeReviewsLookupCommand.php index 3cdbfa7..cd290e5 100644 --- a/app/Dto/AnimeReviewsLookupCommand.php +++ b/app/Dto/AnimeReviewsLookupCommand.php @@ -2,7 +2,9 @@ namespace App\Dto; +use App\Casts\ContextualBooleanCast; use App\Casts\EnumCast; +use App\Dto\Concerns\PreparesData; use App\Enums\MediaReviewsSortEnum; use App\Rules\Attributes\EnumValidation; use Illuminate\Http\JsonResponse; @@ -17,15 +19,17 @@ use Spatie\LaravelData\Optional; */ final class AnimeReviewsLookupCommand extends LookupDataCommand { + use PreparesData; + #[Numeric, Min(1)] public int|Optional $page = 1; #[WithCast(EnumCast::class, MediaReviewsSortEnum::class), EnumValidation(MediaReviewsSortEnum::class)] public MediaReviewsSortEnum|Optional $sort; - #[BooleanType] + #[BooleanType, WithCast(ContextualBooleanCast::class)] public bool|Optional $spoilers; - #[BooleanType] + #[BooleanType, WithCast(ContextualBooleanCast::class)] public bool|Optional $preliminary; } diff --git a/app/Dto/Concerns/HasLimitParameter.php b/app/Dto/Concerns/HasLimitParameter.php index de4dfce..99e7f09 100644 --- a/app/Dto/Concerns/HasLimitParameter.php +++ b/app/Dto/Concerns/HasLimitParameter.php @@ -9,7 +9,7 @@ use Spatie\LaravelData\Optional; trait HasLimitParameter { - use MapsDefaultLimitParameter; + use PreparesData; #[IntegerType, Min(1), MaxLimitWithFallback] public int|Optional $limit; diff --git a/app/Dto/Concerns/HasSfwParameter.php b/app/Dto/Concerns/HasSfwParameter.php new file mode 100644 index 0000000..43bce94 --- /dev/null +++ b/app/Dto/Concerns/HasSfwParameter.php @@ -0,0 +1,16 @@ +has("limit")) - { - /** @noinspection PhpUndefinedFieldInspection */ - $properties->put("limit", Env::get("MAX_RESULTS_PER_PAGE", - property_exists(static::class, "defaultLimit") ? static::$defaultLimit : 25)); - } - - return $properties; - } -} diff --git a/app/Dto/Concerns/PreparesData.php b/app/Dto/Concerns/PreparesData.php new file mode 100644 index 0000000..2bdbd56 --- /dev/null +++ b/app/Dto/Concerns/PreparesData.php @@ -0,0 +1,51 @@ +has("limit")) { + /** @noinspection PhpUndefinedFieldInspection */ + $properties->put("limit", Env::get("MAX_RESULTS_PER_PAGE", + property_exists(static::class, "defaultLimit") ? static::$defaultLimit : 25)); + } + + // we want to cast "true" and "false" string values to boolean before validation, so let's take all properties + // of the class which are bool or bool|Optional type, and using their name read the values from the incoming + // collection, and if they are present have such a value, convert them. + $dataClass = app(DataConfig::class)->getDataClass(static::class); + foreach ($dataClass->properties as $property) { + if (!$property->type->acceptsType("bool")) { + continue; + } + // the name can be different in the $properties variable, so let's check if there is an input name mapping + // for the property and use that instead if present. + $propertyRawName = $property->inputMappedName ?? $property->name; + if ($properties->has($propertyRawName)) { + $propertyVal = $properties->get($propertyRawName); + if ($propertyVal === "true") { + $propertyVal = true; + } + if ($propertyVal === "false") { + $propertyVal = false; + } + $properties->put($propertyRawName, $propertyVal); + } + } + + return $properties; + } +} diff --git a/app/Dto/MangaReviewsLookupCommand.php b/app/Dto/MangaReviewsLookupCommand.php index 611ecb8..cf890ae 100644 --- a/app/Dto/MangaReviewsLookupCommand.php +++ b/app/Dto/MangaReviewsLookupCommand.php @@ -3,7 +3,9 @@ namespace App\Dto; +use App\Casts\ContextualBooleanCast; use App\Casts\EnumCast; +use App\Dto\Concerns\PreparesData; use App\Enums\MediaReviewsSortEnum; use App\Rules\Attributes\EnumValidation; use Illuminate\Http\JsonResponse; @@ -18,15 +20,17 @@ use Spatie\LaravelData\Optional; */ final class MangaReviewsLookupCommand extends LookupDataCommand { + use PreparesData; + #[Numeric, Min(1)] public int|Optional $page = 1; #[WithCast(EnumCast::class, MediaReviewsSortEnum::class), EnumValidation(MediaReviewsSortEnum::class)] public MediaReviewsSortEnum|Optional $sort; - #[BooleanType] + #[BooleanType, WithCast(ContextualBooleanCast::class)] public bool|Optional $spoilers; - #[BooleanType] + #[BooleanType, WithCast(ContextualBooleanCast::class)] public bool|Optional $preliminary; } diff --git a/app/Dto/MediaSearchCommand.php b/app/Dto/MediaSearchCommand.php index 0c1c58c..4029b7b 100644 --- a/app/Dto/MediaSearchCommand.php +++ b/app/Dto/MediaSearchCommand.php @@ -2,6 +2,7 @@ namespace App\Dto; +use App\Dto\Concerns\HasSfwParameter; use Carbon\CarbonImmutable; use Illuminate\Validation\Validator; use Spatie\LaravelData\Attributes\MapInputName; @@ -22,6 +23,8 @@ use Spatie\LaravelData\Transformers\DateTimeInterfaceTransformer; class MediaSearchCommand extends SearchCommand { + use HasSfwParameter; + #[MapInputName("min_score"), MapOutputName("min_score"), Between(0.00, 10.00), Numeric] public float|Optional $minScore; @@ -31,8 +34,6 @@ class MediaSearchCommand extends SearchCommand #[Between(1.00, 9.99), Numeric, Prohibits(["min_score", "max_score"])] public float|Optional $score; - public bool|Optional $sfw; - public string|Optional $genres; #[MapInputName("genres_exclude"), MapOutputName("genres_exclude")] diff --git a/app/Dto/QueryAnimeSchedulesCommand.php b/app/Dto/QueryAnimeSchedulesCommand.php index 1e6f23a..7e24dc3 100644 --- a/app/Dto/QueryAnimeSchedulesCommand.php +++ b/app/Dto/QueryAnimeSchedulesCommand.php @@ -3,11 +3,14 @@ namespace App\Dto; +use App\Casts\ContextualBooleanCast; use App\Casts\EnumCast; use App\Concerns\HasRequestFingerprint; use App\Contracts\DataRequest; use App\Dto\Concerns\HasLimitParameter; use App\Dto\Concerns\HasPageParameter; +use App\Dto\Concerns\HasSfwParameter; +use App\Dto\Concerns\PreparesData; use App\Enums\AnimeScheduleFilterEnum; use App\Rules\Attributes\EnumValidation; use Illuminate\Http\JsonResponse; @@ -22,14 +25,11 @@ use Spatie\LaravelData\Optional; */ final class QueryAnimeSchedulesCommand extends Data implements DataRequest { - use HasLimitParameter, HasRequestFingerprint, HasPageParameter; + use HasLimitParameter, HasRequestFingerprint, HasPageParameter, PreparesData, HasSfwParameter; - #[BooleanType] + #[BooleanType, WithCast(ContextualBooleanCast::class)] public bool|Optional $kids = false; - #[BooleanType] - public bool|Optional $sfw = false; - #[WithCast(EnumCast::class, AnimeScheduleFilterEnum::class), EnumValidation(AnimeScheduleFilterEnum::class)] public ?AnimeScheduleFilterEnum $filter; diff --git a/app/Dto/QueryRandomAnimeCommand.php b/app/Dto/QueryRandomAnimeCommand.php index e77c347..7fc2468 100644 --- a/app/Dto/QueryRandomAnimeCommand.php +++ b/app/Dto/QueryRandomAnimeCommand.php @@ -3,16 +3,14 @@ namespace App\Dto; use App\Contracts\DataRequest; +use App\Dto\Concerns\HasSfwParameter; use App\Http\Resources\V4\AnimeResource; -use Spatie\LaravelData\Attributes\Validation\BooleanType; use Spatie\LaravelData\Data; -use Spatie\LaravelData\Optional; /** * @implements DataRequest */ final class QueryRandomAnimeCommand extends Data implements DataRequest { - #[BooleanType] - public bool|Optional $sfw; + use HasSfwParameter; } diff --git a/app/Dto/QueryRandomMangaCommand.php b/app/Dto/QueryRandomMangaCommand.php index 74b7ab6..5a4fb39 100644 --- a/app/Dto/QueryRandomMangaCommand.php +++ b/app/Dto/QueryRandomMangaCommand.php @@ -3,6 +3,7 @@ namespace App\Dto; use App\Contracts\DataRequest; +use App\Dto\Concerns\HasSfwParameter; use App\Http\Resources\V4\MangaResource; use Spatie\LaravelData\Attributes\Validation\BooleanType; use Spatie\LaravelData\Data; @@ -13,6 +14,5 @@ use Spatie\LaravelData\Optional; */ final class QueryRandomMangaCommand extends Data implements DataRequest { - #[BooleanType] - public bool|Optional $sfw; + use HasSfwParameter; } diff --git a/app/Dto/QueryReviewsCommand.php b/app/Dto/QueryReviewsCommand.php index 1cc3e60..a54e072 100644 --- a/app/Dto/QueryReviewsCommand.php +++ b/app/Dto/QueryReviewsCommand.php @@ -3,10 +3,12 @@ namespace App\Dto; +use App\Casts\ContextualBooleanCast; use App\Casts\EnumCast; use App\Concerns\HasRequestFingerprint; use App\Contracts\DataRequest; use App\Dto\Concerns\HasPageParameter; +use App\Dto\Concerns\PreparesData; use App\Enums\MediaReviewsSortEnum; use App\Http\Resources\V4\ResultsResource; use App\Rules\Attributes\EnumValidation; @@ -20,14 +22,14 @@ use Spatie\LaravelData\Optional; */ abstract class QueryReviewsCommand extends Data implements DataRequest { - use HasRequestFingerprint, HasPageParameter; + use HasRequestFingerprint, HasPageParameter, PreparesData; #[WithCast(EnumCast::class, MediaReviewsSortEnum::class), EnumValidation(MediaReviewsSortEnum::class)] public MediaReviewsSortEnum|Optional $sort; - #[BooleanType] + #[BooleanType, WithCast(ContextualBooleanCast::class)] public bool|Optional $spoilers; - #[BooleanType] + #[BooleanType, WithCast(ContextualBooleanCast::class)] public bool|Optional $preliminary; } diff --git a/app/Dto/QueryTopReviewsCommand.php b/app/Dto/QueryTopReviewsCommand.php index a373f75..2aeeacc 100644 --- a/app/Dto/QueryTopReviewsCommand.php +++ b/app/Dto/QueryTopReviewsCommand.php @@ -2,14 +2,31 @@ namespace App\Dto; +use App\Casts\ContextualBooleanCast; +use App\Casts\EnumCast; use App\Concerns\HasRequestFingerprint; use App\Contracts\DataRequest; +use App\Dto\Concerns\PreparesData; +use App\Enums\TopAnimeFilterEnum; +use App\Enums\TopReviewsTypeEnum; +use App\Rules\Attributes\EnumValidation; use Illuminate\Http\JsonResponse; +use Spatie\LaravelData\Attributes\WithCast; +use Spatie\LaravelData\Optional; /** * @implements DataRequest */ final class QueryTopReviewsCommand extends QueryTopItemsCommand implements DataRequest { - use HasRequestFingerprint; + use HasRequestFingerprint, PreparesData; + + #[WithCast(EnumCast::class, TopAnimeFilterEnum::class), EnumValidation(TopReviewsTypeEnum::class)] + public TopReviewsTypeEnum|Optional $type; + + #[WithCast(ContextualBooleanCast::class)] + public bool|Optional $spoilers; + + #[WithCast(ContextualBooleanCast::class)] + public bool|Optional $preliminary; } diff --git a/app/Enums/AnimeOrderByEnum.php b/app/Enums/AnimeOrderByEnum.php index 6c85b42..a8c0ee2 100644 --- a/app/Enums/AnimeOrderByEnum.php +++ b/app/Enums/AnimeOrderByEnum.php @@ -14,7 +14,7 @@ use Spatie\Enum\Laravel\Enum; * @method static self episodes() * @method static self score() * @method static self scored_by() - * @method static self rank + * @method static self rank() * @method static self popularity() * @method static self members() * @method static self favorites() diff --git a/app/Enums/GenderEnum.php b/app/Enums/GenderEnum.php index 0d193c7..dba5b45 100644 --- a/app/Enums/GenderEnum.php +++ b/app/Enums/GenderEnum.php @@ -3,14 +3,21 @@ namespace App\Enums; use Jikan\Helper\Constants as JikanConstants; +use Spatie\Enum\Laravel\Enum; /** * @method static self any() * @method static self male() * @method static self female() * @method static self nonbinary() + * @OA\Schema( + * schema="users_search_query_gender", + * description="Users Search Query Gender.", + * type="string", + * enum={"any","male","female","nonbinary"} + * ) */ -final class GenderEnum extends \Spatie\Enum\Laravel\Enum +final class GenderEnum extends Enum { protected static function labels(): array { diff --git a/app/Enums/TopReviewsTypeEnum.php b/app/Enums/TopReviewsTypeEnum.php new file mode 100644 index 0000000..9f3c07c --- /dev/null +++ b/app/Enums/TopReviewsTypeEnum.php @@ -0,0 +1,20 @@ + @@ -25,9 +26,12 @@ final class QueryTopReviewsHandler extends RequestHandlerWithScraperCache protected function getScraperData(string $requestFingerPrint, Collection $requestParams): CachedData { + $type = $requestParams->get("type", TopReviewsTypeEnum::anime()->value); + $spoilers = $requestParams->get("spoilers", true); + $preliminary = $requestParams->get("preliminary", true); return $this->scraperService->findList( $requestFingerPrint, - fn (MalClient $jikan, ?int $page = null) => $jikan->getRecentReviews(new RecentReviewsRequest(Constants::RECENT_REVIEW_BEST_VOTED, $page)), + fn (MalClient $jikan, ?int $page = null) => $jikan->getReviews(new ReviewsRequest($type, $page, $spoilers, $preliminary)), $requestParams->get("page")); } } diff --git a/app/GenreAnime.php b/app/GenreAnime.php index 04f2338..e41017f 100644 --- a/app/GenreAnime.php +++ b/app/GenreAnime.php @@ -60,7 +60,7 @@ class GenreAnime extends JikanApiSearchableModel { return [ 'id' => (string) $this->mal_id, - 'mal_id' => (string) $this->mal_id, + 'mal_id' => (int) $this->mal_id, 'name' => $this->name, 'count' => $this->count ]; diff --git a/app/GenreManga.php b/app/GenreManga.php index faaac87..27690da 100644 --- a/app/GenreManga.php +++ b/app/GenreManga.php @@ -59,7 +59,7 @@ class GenreManga extends JikanApiSearchableModel { return [ 'id' => (string) $this->mal_id, - 'mal_id' => (string) $this->mal_id, + 'mal_id' => (int) $this->mal_id, 'name' => $this->name, 'count' => $this->count ]; diff --git a/app/Http/Controllers/V4DB/RandomController.php b/app/Http/Controllers/V4DB/RandomController.php index a2cc9e1..826de4b 100644 --- a/app/Http/Controllers/V4DB/RandomController.php +++ b/app/Http/Controllers/V4DB/RandomController.php @@ -2,33 +2,12 @@ namespace App\Http\Controllers\V4DB; -use App\Anime; -use App\Character; use App\Dto\QueryRandomAnimeCommand; use App\Dto\QueryRandomCharacterCommand; use App\Dto\QueryRandomMangaCommand; use App\Dto\QueryRandomPersonCommand; use App\Dto\QueryRandomUserCommand; -use App\Http\HttpHelper; -use App\Http\HttpResponse; -use App\Http\Resources\V4\AnimeCollection; -use App\Http\Resources\V4\AnimeResource; -use App\Http\Resources\V4\CharacterCollection; -use App\Http\Resources\V4\CharacterResource; -use App\Http\Resources\V4\CommonResource; -use App\Http\Resources\V4\MangaCollection; -use App\Http\Resources\V4\MangaResource; -use App\Http\Resources\V4\PersonCollection; -use App\Http\Resources\V4\PersonResource; -use App\Http\Resources\V4\ProfileResource; -use App\Http\Resources\V4\ResultsResource; -use App\Http\Resources\V4\UserCollection; -use App\Manga; -use App\Person; -use App\Profile; -use App\User; -use Illuminate\Http\Request; -use MongoDB\BSON\UTCDateTime; + class RandomController extends Controller { diff --git a/app/Http/Controllers/V4DB/TopController.php b/app/Http/Controllers/V4DB/TopController.php index f941331..91eab31 100644 --- a/app/Http/Controllers/V4DB/TopController.php +++ b/app/Http/Controllers/V4DB/TopController.php @@ -28,7 +28,7 @@ class TopController extends Controller * name="filter", * in="query", * required=false, - * @OA\Schema(ref="#/components/schemas/top_anime_filter) + * @OA\Schema(ref="#/components/schemas/top_anime_filter") * ), * * @OA\Parameter( @@ -82,7 +82,7 @@ class TopController extends Controller * name="filter", * in="query", * required=false, - * @OA\Schema(ref="#/components/schemas/top_manga_filter) + * @OA\Schema(ref="#/components/schemas/top_manga_filter") * ), * * @OA\Parameter(ref="#/components/parameters/page"), @@ -168,6 +168,29 @@ class TopController extends Controller * * @OA\Parameter(ref="#/components/parameters/page"), * + * @OA\Parameter( + * name="type", + * in="query", + * required=false, + * @OA\Schema(ref="#/components/schemas/top_reviews_type_enum") + * ), + * + * @OA\Parameter( + * name="preliminary", + * in="query", + * required=false, + * description="Whether the results include preliminary reviews or not. Defaults to true.", + * @OA\Schema(type="boolean") + * ), + * + * @OA\Parameter( + * name="spoilers", + * in="query", + * required=false, + * description="Whether the results include reviews with spoilers or not. Defaults to true.", + * @OA\Schema(type="boolean") + * ), + * * @OA\Response( * response="200", * description="Returns top reviews", diff --git a/app/Http/HttpHelper.php b/app/Http/HttpHelper.php index 2c06f41..fcaa7dc 100644 --- a/app/Http/HttpHelper.php +++ b/app/Http/HttpHelper.php @@ -38,32 +38,16 @@ class HttpHelper return (int) str_replace('v', '', $request->segment(1)); } - public static function serializeEmptyObjects(string $requestType, array $data) + public static function serializeEmptyObjects(string $requestType, array $data): array { if (!($requestType === 'anime' || $requestType === 'manga')) { return $data; } - if (isset($data['related']) && \count($data['related']) === 0) { - $data['related'] = new \stdClass(); - } - - if (isset($data['related'])) { - $related = $data['related']; - $data['related'] = []; - - foreach ($related as $relation => $items) { - $data['related'][] = [ - 'relation' => $relation, - 'entry' => $items - ]; - } - } - - return $data; + return self::serializeEmptyObjectsControllerLevel($data); } - public static function serializeEmptyObjectsControllerLevel(array $data) + public static function serializeEmptyObjectsControllerLevel(array $data): array { if (isset($data['related']) && \count($data['related']) === 0) { $data['related'] = new \stdClass(); diff --git a/app/Http/Resources/V4/ReviewsResource.php b/app/Http/Resources/V4/ReviewsResource.php index d6a64cb..256beb2 100644 --- a/app/Http/Resources/V4/ReviewsResource.php +++ b/app/Http/Resources/V4/ReviewsResource.php @@ -94,12 +94,12 @@ class ReviewsResource extends JsonResource * ), * @OA\Property ( * property="is_spoiler", - * type="bool", + * type="boolean", * description="The review contains spoiler" * ), * @OA\Property ( * property="is_preliminary", - * type="bool", + * type="boolean", * description="The review was made before the entry was completed" * ), * ), @@ -190,12 +190,12 @@ class ReviewsResource extends JsonResource * ), * @OA\Property ( * property="is_spoiler", - * type="bool", + * type="boolean", * description="The review contains spoiler" * ), * @OA\Property ( * property="is_preliminary", - * type="bool", + * type="boolean", * description="The review was made before the entry was completed" * ), * @OA\Property( diff --git a/app/JikanApiModel.php b/app/JikanApiModel.php index 8bf99b2..9ae0f56 100644 --- a/app/JikanApiModel.php +++ b/app/JikanApiModel.php @@ -21,7 +21,7 @@ class JikanApiModel extends \Jenssegers\Mongodb\Eloquent\Model /** @noinspection PhpUnused */ public function scopeRandom(Builder $query, int $numberOfRandomItems = 1): Collection { - return $query->raw(fn(\MongoDB\Collection $collection) => $collection->aggregate([ + return $query->raw(fn(\Jenssegers\Mongodb\Collection $collection) => $collection->aggregate([ ['$sample' => ['size' => $numberOfRandomItems]] ])); } diff --git a/app/Magazine.php b/app/Magazine.php index 690cce0..6641953 100644 --- a/app/Magazine.php +++ b/app/Magazine.php @@ -64,7 +64,7 @@ class Magazine extends JikanApiSearchableModel { return [ 'id' => (string) $this->mal_id, - 'mal_id' => (string) $this->mal_id, + 'mal_id' => (int) $this->mal_id, 'name' => $this->name, 'count' => $this->count ]; diff --git a/app/Manga.php b/app/Manga.php index 86fc0ec..1e68c8d 100644 --- a/app/Manga.php +++ b/app/Manga.php @@ -121,7 +121,7 @@ class Manga extends JikanApiSearchableModel { return [ 'id' => (string) $this->mal_id, - 'mal_id' => (string) $this->mal_id, + 'mal_id' => (int) $this->mal_id, 'start_date' => $this->convertToTimestamp($this->published['from']), 'end_date' => $this->convertToTimestamp($this->published['to']), 'title' => $this->title, diff --git a/app/Person.php b/app/Person.php index f05527d..076b0a0 100644 --- a/app/Person.php +++ b/app/Person.php @@ -76,7 +76,7 @@ class Person extends JikanApiSearchableModel { return [ 'id' => (string) $this->mal_id, - 'mal_id' => (string) $this->mal_id, + 'mal_id' => (int) $this->mal_id, 'name' => $this->name, 'given_name' => $this->given_name, 'family_name' => $this->family_name, diff --git a/app/Producers.php b/app/Producers.php index 261cc8c..59ab7ab 100644 --- a/app/Producers.php +++ b/app/Producers.php @@ -59,9 +59,12 @@ class Producers extends JikanApiSearchableModel { return [ 'id' => (string) $this->mal_id, - 'mal_id' => (string) $this->mal_id, - 'url' => !is_null($this->url) ? collect(explode('/', $this->url))->last() : '', - 'titles' => !is_null($this->titles) ? $this->titles : [''] + 'mal_id' => (int) $this->mal_id, + 'url' => !is_null($this->url) ? $this->url : '', + 'titles' => !is_null($this->titles) ? collect($this->titles)->map(fn ($x) => $x["title"])->toArray() : [''], + 'established' => $this->convertToTimestamp($this->established), + 'favorites' => $this->favorites, + 'count' => $this->count ]; } diff --git a/app/Profile.php b/app/Profile.php index 60e507a..86313bb 100644 --- a/app/Profile.php +++ b/app/Profile.php @@ -8,7 +8,7 @@ use Jikan\Request\User\UserProfileRequest; class Profile extends JikanApiSearchableModel { use FilteredByLetter; - protected array $filters = ["order_by", "sort", "letter"]; + protected array $filters = []; /** * The attributes that are mass assignable. @@ -61,7 +61,7 @@ class Profile extends JikanApiSearchableModel { return [ 'id' => (string) $this->mal_id, - 'mal_id' => (string) $this->mal_id, + 'mal_id' => (int) $this->mal_id, 'username' => $this->username ]; } diff --git a/app/Support/helpers.php b/app/Support/helpers.php index ab94556..0ed100a 100644 --- a/app/Support/helpers.php +++ b/app/Support/helpers.php @@ -39,3 +39,18 @@ if (!function_exists('rescue')) { } } } + +if (!function_exists('to_boolean')) { + + /** + * Convert to boolean + * + * @param $booleable + * @return boolean + */ + function to_boolean($booleable): bool + { + return filter_var($booleable, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE); + } +} + diff --git a/storage/api-docs/api-docs.json b/storage/api-docs/api-docs.json index 4bf22c8..892a139 100644 --- a/storage/api-docs/api-docs.json +++ b/storage/api-docs/api-docs.json @@ -2982,6 +2982,53 @@ } } }, + "/seasons/now": { + "get": { + "tags": [ + "seasons" + ], + "operationId": "getSeasonNow", + "parameters": [ + { + "$ref": "#/components/parameters/page" + }, + { + "$ref": "#/components/parameters/limit" + }, + { + "name": "filter", + "in": "query", + "description": "Entry types", + "schema": { + "type": "string", + "enum": [ + "tv", + "movie", + "ova", + "special", + "ona", + "music" + ] + } + } + ], + "responses": { + "200": { + "description": "Returns current seasonal anime", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/anime_search" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, "/seasons/{year}/{season}": { "get": { "tags": [ @@ -3023,6 +3070,9 @@ }, { "$ref": "#/components/parameters/page" + }, + { + "$ref": "#/components/parameters/limit" } ], "responses": { @@ -3042,34 +3092,6 @@ } } }, - "/seasons/now": { - "get": { - "tags": [ - "seasons" - ], - "operationId": "getSeasonNow", - "parameters": [ - { - "$ref": "#/components/parameters/page" - } - ], - "responses": { - "200": { - "description": "Returns current seasonal anime", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/anime_search" - } - } - } - }, - "400": { - "description": "Error: Bad request. When required parameters were not supplied." - } - } - } - }, "/seasons": { "get": { "tags": [ @@ -3118,6 +3140,9 @@ }, { "$ref": "#/components/parameters/page" + }, + { + "$ref": "#/components/parameters/limit" } ], "responses": { @@ -3157,13 +3182,22 @@ "in": "query", "required": false, "schema": { - "type": "string", - "enum": [ - "airing", - "upcoming", - "bypopularity", - "favorite" - ] + "$ref": "#/components/schemas/top_anime_filter" + } + }, + { + "name": "rating", + "in": "query", + "schema": { + "$ref": "#/components/schemas/anime_search_query_rating" + } + }, + { + "name": "sfw", + "in": "query", + "description": "Filter out Adult entries", + "schema": { + "type": "boolean" } }, { @@ -3210,13 +3244,7 @@ "in": "query", "required": false, "schema": { - "type": "string", - "enum": [ - "publishing", - "upcoming", - "bypopularity", - "favorite" - ] + "$ref": "#/components/schemas/top_manga_filter" } }, { @@ -3314,6 +3342,32 @@ "parameters": [ { "$ref": "#/components/parameters/page" + }, + { + "name": "type", + "in": "query", + "required": false, + "schema": { + "$ref": "#/components/schemas/top_reviews_type_enum" + } + }, + { + "name": "preliminary", + "in": "query", + "description": "Whether the results include preliminary reviews or not. Defaults to true.", + "required": false, + "schema": { + "type": "boolean" + } + }, + { + "name": "spoilers", + "in": "query", + "description": "Whether the results include reviews with spoilers or not. Defaults to true.", + "required": false, + "schema": { + "type": "boolean" + } } ], "responses": { @@ -3974,11 +4028,6 @@ "watch" ], "operationId": "getWatchRecentEpisodes", - "parameters": [ - { - "$ref": "#/components/parameters/limit" - } - ], "responses": { "200": { "description": "Returns Recently Added Episodes", @@ -4002,11 +4051,6 @@ "watch" ], "operationId": "getWatchPopularEpisodes", - "parameters": [ - { - "$ref": "#/components/parameters/limit" - } - ], "responses": { "200": { "description": "Returns Popular Episodes", @@ -4030,6 +4074,11 @@ "watch" ], "operationId": "getWatchRecentPromos", + "parameters": [ + { + "$ref": "#/components/parameters/page" + } + ], "responses": { "200": { "description": "Returns Recently Added Promotional Videos", @@ -4053,11 +4102,6 @@ "watch" ], "operationId": "getWatchPopularPromos", - "parameters": [ - { - "$ref": "#/components/parameters/limit" - } - ], "responses": { "200": { "description": "Returns Popular Promotional Videos", @@ -4078,6 +4122,231 @@ }, "components": { "schemas": { + "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" + ] + }, + "anime_search_query_rating": { + "description": "Available Anime audience ratings

Ratings
  • G - All Ages
  • PG - Children
  • PG-13 - Teens 13 or older
  • R - 17+ (violence & profanity)
  • R+ - Mild Nudity
  • Rx - Hentai
", + "type": "string", + "enum": [ + "g", + "pg", + "pg13", + "r17", + "r", + "rx" + ] + }, + "anime_search_query_status": { + "description": "Available Anime statuses", + "type": "string", + "enum": [ + "airing", + "complete", + "upcoming" + ] + }, + "anime_search_query_type": { + "description": "Available Anime types", + "type": "string", + "enum": [ + "tv", + "movie", + "ova", + "special", + "ona", + "music" + ] + }, + "characters_search_query_orderby": { + "description": "Available Character order_by properties", + "type": "string", + "enum": [ + "mal_id", + "name", + "favorites" + ] + }, + "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" + ] + }, + "club_search_query_orderby": { + "description": "Club Search Query OrderBy", + "type": "string", + "enum": [ + "mal_id", + "name", + "members_count", + "created" + ] + }, + "club_search_query_type": { + "description": "Club Search Query Type", + "type": "string", + "enum": [ + "public", + "private", + "secret" + ] + }, + "users_search_query_gender": { + "description": "Users Search Query Gender.", + "type": "string", + "enum": [ + "any", + "male", + "female", + "nonbinary" + ] + }, + "genre_query_filter": { + "description": "Filter genres by type", + "type": "string", + "enum": [ + "genres", + "explicit_genres", + "themes", + "demographics" + ] + }, + "magazines_query_orderby": { + "description": "Order by magazine data", + "type": "string", + "enum": [ + "mal_id", + "name", + "count" + ] + }, + "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" + ] + }, + "manga_search_query_status": { + "description": "Available Manga statuses", + "type": "string", + "enum": [ + "publishing", + "complete", + "hiatus", + "discontinued", + "upcoming" + ] + }, + "manga_search_query_type": { + "description": "Available Manga types", + "type": "string", + "enum": [ + "manga", + "novel", + "lightnovel", + "oneshot", + "doujin", + "manhwa", + "manhua" + ] + }, + "people_search_query_orderby": { + "description": "Available People order_by properties", + "type": "string", + "enum": [ + "mal_id", + "name", + "birthday", + "favorites" + ] + }, + "producers_query_orderby": { + "description": "Producers Search Query Order By", + "type": "string", + "enum": [ + "mal_id", + "count", + "favorites", + "established" + ] + }, + "search_query_sort": { + "description": "Search query sort direction", + "type": "string", + "enum": [ + "desc", + "asc" + ] + }, + "top_anime_filter": { + "description": "Top items filter types", + "type": "string", + "enum": [ + "airing", + "upcoming", + "bypopularity", + "favorite" + ] + }, + "top_manga_filter": { + "description": "Top items filter types", + "type": "string", + "enum": [ + "publishing", + "upcoming", + "bypopularity", + "favorite" + ] + }, + "top_reviews_type_enum": { + "description": "The type of reviews to filter by. Defaults to anime.", + "type": "string", + "enum": [ + "anime", + "manga" + ] + }, "anime_episodes": { "description": "Anime Episodes Resource", "allOf": [ @@ -4231,15 +4500,6 @@ }, "type": "object" }, - "magazines_query_orderby": { - "description": "Order by magazine data", - "type": "string", - "enum": [ - "mal_id", - "name", - "count" - ] - }, "manga_news": { "description": "Manga News Resource", "allOf": [ @@ -4320,14 +4580,6 @@ } ] }, - "search_query_sort": { - "description": "Characters Search Query Sort", - "type": "string", - "enum": [ - "desc", - "asc" - ] - }, "users_search": { "description": "User Results", "allOf": [ @@ -4364,16 +4616,6 @@ } ] }, - "producers_query_orderby": { - "description": "Producers Search Query Order By", - "type": "string", - "enum": [ - "mal_id", - "count", - "favorites", - "established" - ] - }, "seasons": { "description": "List of available seasons", "properties": { @@ -4590,167 +4832,6 @@ } ] }, - "anime_search_query_type": { - "description": "Available Anime types", - "type": "string", - "enum": [ - "tv", - "movie", - "ova", - "special", - "ona", - "music" - ] - }, - "anime_search_query_status": { - "description": "Available Anime statuses", - "type": "string", - "enum": [ - "airing", - "complete", - "upcoming" - ] - }, - "anime_search_query_rating": { - "description": "Available Anime audience ratings

Ratings
  • G - All Ages
  • PG - Children
  • PG-13 - Teens 13 or older
  • R - 17+ (violence & profanity)
  • R+ - Mild Nudity
  • Rx - Hentai
", - "type": "string", - "enum": [ - "g", - "pg", - "pg13", - "r17", - "r", - "rx" - ] - }, - "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" - ] - }, - "characters_search_query_orderby": { - "description": "Available Character order_by properties", - "type": "string", - "enum": [ - "mal_id", - "name", - "favorites" - ] - }, - "club_search_query_type": { - "description": "Club Search Query Type", - "type": "string", - "enum": [ - "public", - "private", - "secret" - ] - }, - "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" - ] - }, - "club_search_query_orderby": { - "description": "Club Search Query OrderBy", - "type": "string", - "enum": [ - "mal_id", - "title", - "members_count", - "pictures_count", - "created" - ] - }, - "manga_search_query_type": { - "description": "Available Manga types", - "type": "string", - "enum": [ - "manga", - "novel", - "lightnovel", - "oneshot", - "doujin", - "manhwa", - "manhua" - ] - }, - "manga_search_query_status": { - "description": "Available Manga statuses", - "type": "string", - "enum": [ - "publishing", - "complete", - "hiatus", - "discontinued", - "upcoming" - ] - }, - "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" - ] - }, - "people_search_query_orderby": { - "description": "Available People order_by properties", - "type": "string", - "enum": [ - "mal_id", - "name", - "birthday", - "favorites" - ] - }, - "users_search_query_gender": { - "description": "Users Search Query Gender", - "type": "string", - "enum": [ - "any", - "male", - "female", - "nonbinary" - ] - }, "anime_characters": { "description": "Anime Characters Resource", "properties": { @@ -6649,16 +6730,6 @@ }, "type": "object" }, - "genre_query_filter": { - "description": "Filter genres by type", - "type": "string", - "enum": [ - "genres", - "explicit_genres", - "themes", - "demographics" - ] - }, "genre": { "description": "Genre Resource", "properties": { @@ -8564,11 +8635,11 @@ }, "is_spoiler": { "description": "The review contains spoiler", - "type": "bool" + "type": "boolean" }, "is_preliminary": { "description": "The review was made before the entry was completed", - "type": "bool" + "type": "boolean" } }, "type": "object" @@ -8646,11 +8717,11 @@ }, "is_spoiler": { "description": "The review contains spoiler", - "type": "bool" + "type": "boolean" }, "is_preliminary": { "description": "The review was made before the entry was completed", - "type": "bool" + "type": "boolean" }, "episodes_watched": { "description": "Number of episodes watched",