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