From 77b1bbd80b81f93eb57a4262d31c9c764d85d125 Mon Sep 17 00:00:00 2001 From: pushrbx Date: Tue, 9 Apr 2024 20:31:37 +0100 Subject: [PATCH] fixed a bug around season endpoints - added a new parameter: "continuing" - continuing items from previous seasons are now excluded by default - should fix #521 --- app/Contracts/AnimeRepository.php | 5 ++- app/Dto/Concerns/HasContinuingParameter.php | 26 +++++++++++ app/Dto/Concerns/PreparesData.php | 3 +- app/Dto/QueryAnimeSeasonCommand.php | 9 +++- app/Features/QueryAnimeSeasonHandlerBase.php | 2 - .../QueryCurrentAnimeSeasonHandler.php | 3 +- .../QuerySpecificAnimeSeasonHandler.php | 4 +- .../Controllers/V4DB/SeasonController.php | 14 ++---- app/Repositories/DefaultAnimeRepository.php | 18 +++++--- storage/api-docs/api-docs.json | 20 ++++++++- tests/Integration/SeasonControllerTest.php | 44 ++++++++++++++++++- 11 files changed, 119 insertions(+), 29 deletions(-) create mode 100644 app/Dto/Concerns/HasContinuingParameter.php diff --git a/app/Contracts/AnimeRepository.php b/app/Contracts/AnimeRepository.php index c0d734a..ee5b484 100644 --- a/app/Contracts/AnimeRepository.php +++ b/app/Contracts/AnimeRepository.php @@ -39,11 +39,12 @@ interface AnimeRepository extends Repository ?AnimeScheduleFilterEnum $filter = null ): EloquentBuilder; - public function getAiredBetween( + public function getItemsBySeason( Carbon $from, Carbon $to, ?AnimeTypeEnum $type = null, - ?string $premiered = null + ?string $premiered = null, + bool $includeContinuingItems = false ): EloquentBuilder; public function getUpcomingSeasonItems(?AnimeTypeEnum $type = null): EloquentBuilder; diff --git a/app/Dto/Concerns/HasContinuingParameter.php b/app/Dto/Concerns/HasContinuingParameter.php new file mode 100644 index 0000000..f6244ad --- /dev/null +++ b/app/Dto/Concerns/HasContinuingParameter.php @@ -0,0 +1,26 @@ +Example usage: `?continuing`", + * @OA\Schema(type="boolean") + * ), + */ +trait HasContinuingParameter +{ + use PreparesData; + + #[BooleanType, WithCast(ContextualBooleanCast::class)] + public bool|Optional $continuing = false; +} diff --git a/app/Dto/Concerns/PreparesData.php b/app/Dto/Concerns/PreparesData.php index 9adc76b..d8c5cb0 100644 --- a/app/Dto/Concerns/PreparesData.php +++ b/app/Dto/Concerns/PreparesData.php @@ -17,7 +17,6 @@ trait PreparesData { // let's always set the limit parameter to the globally configured default value if (property_exists(static::class, "limit") && !$properties->has("limit")) { - /** @noinspection PhpUndefinedFieldInspection */ $properties->put("limit", max_results_per_page( property_exists(static::class, "defaultLimit") ? static::$defaultLimit : null)); } @@ -53,7 +52,7 @@ trait PreparesData } else { $properties->forget($propertyRawName); } - } + } } return $properties; diff --git a/app/Dto/QueryAnimeSeasonCommand.php b/app/Dto/QueryAnimeSeasonCommand.php index 3b7c875..f53c136 100644 --- a/app/Dto/QueryAnimeSeasonCommand.php +++ b/app/Dto/QueryAnimeSeasonCommand.php @@ -11,6 +11,7 @@ use App\Dto\Concerns\HasLimitParameter; use App\Dto\Concerns\HasPageParameter; use App\Dto\Concerns\HasSfwParameter; use App\Dto\Concerns\HasUnapprovedParameter; +use App\Dto\Concerns\HasContinuingParameter; use App\Enums\AnimeTypeEnum; use App\Rules\Attributes\EnumValidation; use Spatie\LaravelData\Attributes\WithCast; @@ -20,7 +21,13 @@ use Spatie\LaravelData\Optional; abstract class QueryAnimeSeasonCommand extends Data implements DataRequest { - use HasSfwParameter, HasKidsParameter, HasUnapprovedParameter, HasLimitParameter, HasRequestFingerprint, HasPageParameter; + use HasSfwParameter, + HasKidsParameter, + HasUnapprovedParameter, + HasLimitParameter, + HasRequestFingerprint, + HasPageParameter, + HasContinuingParameter; #[WithCast(EnumCast::class, AnimeTypeEnum::class), EnumValidation(AnimeTypeEnum::class)] public AnimeTypeEnum|Optional $filter; diff --git a/app/Features/QueryAnimeSeasonHandlerBase.php b/app/Features/QueryAnimeSeasonHandlerBase.php index 778cd07..67a8ced 100644 --- a/app/Features/QueryAnimeSeasonHandlerBase.php +++ b/app/Features/QueryAnimeSeasonHandlerBase.php @@ -32,8 +32,6 @@ abstract class QueryAnimeSeasonHandlerBase implements RequestHandler { $requestParams = collect($request->all()); $type = $requestParams->has("filter") ? $request->filter : null; - $season = $requestParams->has("season") ? $request->season : null; - $year = $requestParams->has("year") ? $request->year : null; $results = $this->getSeasonItems($request, $type); // apply sfw, kids and unapproved filters /** @noinspection PhpUndefinedMethodInspection */ diff --git a/app/Features/QueryCurrentAnimeSeasonHandler.php b/app/Features/QueryCurrentAnimeSeasonHandler.php index da7d427..972d36b 100644 --- a/app/Features/QueryCurrentAnimeSeasonHandler.php +++ b/app/Features/QueryCurrentAnimeSeasonHandler.php @@ -53,7 +53,8 @@ final class QueryCurrentAnimeSeasonHandler extends QueryAnimeSeasonHandlerBase */ [$from, $to] = $this->getSeasonRange($year, $season); $premiered = ucfirst($season)." {$year}"; + $includeContinuingItems = $request->continuing; - return $this->repository->getAiredBetween($from, $to, $type, $premiered); + return $this->repository->getItemsBySeason($from, $to, $type, $premiered, $includeContinuingItems); } } diff --git a/app/Features/QuerySpecificAnimeSeasonHandler.php b/app/Features/QuerySpecificAnimeSeasonHandler.php index f133c30..1ec6821 100644 --- a/app/Features/QuerySpecificAnimeSeasonHandler.php +++ b/app/Features/QuerySpecificAnimeSeasonHandler.php @@ -28,8 +28,8 @@ final class QuerySpecificAnimeSeasonHandler extends QueryAnimeSeasonHandlerBase [$from, $to] = $this->getSeasonRange($request->year, $request->season); $premiered = ucfirst($request->season)." {$request->year}"; + $includeContinuingItems = $request->continuing; - return $this->repository->getAiredBetween($from, $to, $type, $premiered); -// ->where("status", "!=", AnimeStatusEnum::upcoming()->label); + return $this->repository->getItemsBySeason($from, $to, $type, $premiered, $includeContinuingItems); } } diff --git a/app/Http/Controllers/V4DB/SeasonController.php b/app/Http/Controllers/V4DB/SeasonController.php index a70a0cd..aa2c091 100644 --- a/app/Http/Controllers/V4DB/SeasonController.php +++ b/app/Http/Controllers/V4DB/SeasonController.php @@ -2,21 +2,12 @@ namespace App\Http\Controllers\V4DB; -use App\Anime; use App\Dto\QueryAnimeSeasonListCommand; use App\Dto\QueryCurrentAnimeSeasonCommand; use App\Dto\QuerySpecificAnimeSeasonCommand; use App\Dto\QueryUpcomingAnimeSeasonCommand; -use App\Http\HttpResponse; -use App\Http\QueryBuilder\AnimeSearchQueryBuilder; -use App\Http\Resources\V4\AnimeCollection; -use App\Http\Resources\V4\ResultsResource; use Exception; -use Illuminate\Http\Request; -use Illuminate\Support\Carbon; -use Illuminate\Support\Facades\DB; -use Jikan\Request\SeasonList\SeasonListRequest; -use Symfony\Component\HttpFoundation\Exception\BadRequestException; +use OpenApi\Annotations as OA; /** * @@ -38,6 +29,7 @@ class SeasonController extends Controller * * @OA\Parameter(ref="#/components/parameters/sfw"), * @OA\Parameter(ref="#/components/parameters/unapproved"), + * @OA\Parameter(ref="#/components/parameters/continuing"), * @OA\Parameter(ref="#/components/parameters/page"), * @OA\Parameter(ref="#/components/parameters/limit"), * @@ -89,6 +81,7 @@ class SeasonController extends Controller * * @OA\Parameter(ref="#/components/parameters/sfw"), * @OA\Parameter(ref="#/components/parameters/unapproved"), + * @OA\Parameter(ref="#/components/parameters/continuing"), * @OA\Parameter(ref="#/components/parameters/page"), * @OA\Parameter(ref="#/components/parameters/limit"), * @@ -177,6 +170,7 @@ class SeasonController extends Controller * * @OA\Parameter(ref="#/components/parameters/sfw"), * @OA\Parameter(ref="#/components/parameters/unapproved"), + * @OA\Parameter(ref="#/components/parameters/continuing"), * @OA\Parameter(ref="#/components/parameters/page"), * @OA\Parameter(ref="#/components/parameters/limit"), * diff --git a/app/Repositories/DefaultAnimeRepository.php b/app/Repositories/DefaultAnimeRepository.php index 4a29a48..76d9a34 100644 --- a/app/Repositories/DefaultAnimeRepository.php +++ b/app/Repositories/DefaultAnimeRepository.php @@ -110,11 +110,12 @@ final class DefaultAnimeRepository extends DatabaseRepository implements AnimeRe return $queryable; } - public function getAiredBetween( + public function getItemsBySeason( Carbon $from, Carbon $to, ?AnimeTypeEnum $type = null, - ?string $premiered = null + ?string $premiered = null, + bool $includeContinuingItems = false ): EloquentBuilder { $queryable = $this->queryable(true); @@ -127,9 +128,10 @@ final class DefaultAnimeRepository extends DatabaseRepository implements AnimeRe $finalFilter = []; // if the premiered parameter for the filter is not null, look for those items which have a premiered attribute set, - // and equals to the parameter value, OR look for those items which doesn't have premired attribute set, + // and equals to the parameter value, OR look for those items which doesn't have premiered attribute set, // they don't have a garbled aired string and their aired.from date is within the from-to parameters range. - // Additionally, we want to include all those items which are carry overs from previous seasons. + // Additionally, we want to include all those items which are carry overs from previous seasons, + // if the includeContinuingItems argument is set to true. if ($premiered !== null) { $finalFilter['$or'] = [ ['premiered' => $premiered], @@ -140,12 +142,14 @@ final class DefaultAnimeRepository extends DatabaseRepository implements AnimeRe ], ...$airedFilter ], + ]; + if ($includeContinuingItems) { // this condition will include "continuing" items from previous seasons - [ + $finalFilter['$or'][] = [ 'aired.from' => ['$lte' => $from->toAtomString()], 'airing' => true - ] - ]; + ]; + } } else { $finalFilter = array_merge($finalFilter, $airedFilter); $finalFilter['aired.string'] = [ diff --git a/storage/api-docs/api-docs.json b/storage/api-docs/api-docs.json index c9108d3..8ab520a 100644 --- a/storage/api-docs/api-docs.json +++ b/storage/api-docs/api-docs.json @@ -3046,6 +3046,9 @@ { "$ref": "#/components/parameters/unapproved" }, + { + "$ref": "#/components/parameters/continuing" + }, { "$ref": "#/components/parameters/page" }, @@ -3115,6 +3118,9 @@ { "$ref": "#/components/parameters/unapproved" }, + { + "$ref": "#/components/parameters/continuing" + }, { "$ref": "#/components/parameters/page" }, @@ -3191,6 +3197,9 @@ { "$ref": "#/components/parameters/unapproved" }, + { + "$ref": "#/components/parameters/continuing" + }, { "$ref": "#/components/parameters/page" }, @@ -9027,6 +9036,15 @@ } }, "parameters": { + "continuing": { + "name": "continuing", + "in": "query", + "description": "This is a flag. When supplied it will include entries which are continuing from previous seasons. MAL includes these items on the seasons view in the ″TV (continuing)″ section. (Example: https://myanimelist.net/anime/season/2024/winter)
Example usage: `?continuing`", + "required": false, + "schema": { + "type": "boolean" + } + }, "kids": { "name": "kids", "in": "query", @@ -9092,4 +9110,4 @@ "description": "About", "url": "https://jikan.moe" } -} +} \ No newline at end of file diff --git a/tests/Integration/SeasonControllerTest.php b/tests/Integration/SeasonControllerTest.php index 44a71db..e85f77e 100644 --- a/tests/Integration/SeasonControllerTest.php +++ b/tests/Integration/SeasonControllerTest.php @@ -131,9 +131,51 @@ class SeasonControllerTest extends TestCase $state["airing"] = true; $f->create($state); - $content = $this->getJsonResponse([], "/v4/seasons/2024/winter"); + $content = $this->getJsonResponse([], "/v4/seasons/2024/winter?continuing=true"); $this->seeStatusCode(200); $this->assertIsArray($content["data"]); $this->assertCount(3, $content["data"]); } + + public function testShouldNotIncludeContinuingItemsByDefault() + { + Carbon::setTestNow(Carbon::parse("2024-01-11")); + // an item in the future airing + $f = Anime::factory(1); + $startDate = "2024-02-24"; + $carbonStartDate = Carbon::parse($startDate); + $state = $f->serializeStateDefinition([ + "aired" => new CarbonDateRange($carbonStartDate, null) + ]); + $state["aired"]["string"] = "Feb 24, 2024 to ?"; + $state["premiered"] = null; + $state["status"] = "Not yet aired"; + $state["airing"] = false; + $f->create($state); + + // the absolutely correct item + $f = Anime::factory(1); + $state = $f->serializeStateDefinition([ + "aired" => new CarbonDateRange(Carbon::parse("2024-01-10"), Carbon::parse("2024-02-15")) + ]); + $state["premiered"] = "Winter 2024"; + $state["status"] = "Currently Airing"; + $state["airing"] = true; + $f->create($state); + + // the continuing item + $f = Anime::factory(1); + $state = $f->serializeStateDefinition([ + "aired" => new CarbonDateRange(Carbon::parse("2023-10-10"), null) + ]); + $state["premiered"] = "Fall 2023"; + $state["status"] = "Currently Airing"; + $state["airing"] = true; + $f->create($state); + + $content = $this->getJsonResponse([], "/v4/seasons/2024/winter"); + $this->seeStatusCode(200); + $this->assertIsArray($content["data"]); + $this->assertCount(2, $content["data"]); + } }