fixed a bug around season endpoints

- added a new parameter: "continuing"
- continuing items from previous seasons are now excluded by default
- should fix #521
This commit is contained in:
pushrbx 2024-04-09 20:31:37 +01:00
parent 3311db2857
commit 77b1bbd80b
No known key found for this signature in database
GPG Key ID: A16A474BBC2C91D9
11 changed files with 119 additions and 29 deletions

View File

@ -39,11 +39,12 @@ interface AnimeRepository extends Repository
?AnimeScheduleFilterEnum $filter = null ?AnimeScheduleFilterEnum $filter = null
): EloquentBuilder; ): EloquentBuilder;
public function getAiredBetween( public function getItemsBySeason(
Carbon $from, Carbon $from,
Carbon $to, Carbon $to,
?AnimeTypeEnum $type = null, ?AnimeTypeEnum $type = null,
?string $premiered = null ?string $premiered = null,
bool $includeContinuingItems = false
): EloquentBuilder; ): EloquentBuilder;
public function getUpcomingSeasonItems(?AnimeTypeEnum $type = null): EloquentBuilder; public function getUpcomingSeasonItems(?AnimeTypeEnum $type = null): EloquentBuilder;

View File

@ -0,0 +1,26 @@
<?php
namespace App\Dto\Concerns;
use App\Casts\ContextualBooleanCast;
use OpenApi\Annotations as OA;
use Spatie\LaravelData\Attributes\Validation\BooleanType;
use Spatie\LaravelData\Attributes\WithCast;
use Spatie\LaravelData\Optional;
/**
* @OA\Parameter(
* name="continuing",
* in="query",
* required=false,
* 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 &#8243;TV (continuing)&#8243; section. (Example: https://myanimelist.net/anime/season/2024/winter) <br />Example usage: `?continuing`",
* @OA\Schema(type="boolean")
* ),
*/
trait HasContinuingParameter
{
use PreparesData;
#[BooleanType, WithCast(ContextualBooleanCast::class)]
public bool|Optional $continuing = false;
}

View File

@ -17,7 +17,6 @@ trait PreparesData
{ {
// let's always set the limit parameter to the globally configured default value // let's always set the limit parameter to the globally configured default value
if (property_exists(static::class, "limit") && !$properties->has("limit")) { if (property_exists(static::class, "limit") && !$properties->has("limit")) {
/** @noinspection PhpUndefinedFieldInspection */
$properties->put("limit", max_results_per_page( $properties->put("limit", max_results_per_page(
property_exists(static::class, "defaultLimit") ? static::$defaultLimit : null)); property_exists(static::class, "defaultLimit") ? static::$defaultLimit : null));
} }
@ -53,7 +52,7 @@ trait PreparesData
} else { } else {
$properties->forget($propertyRawName); $properties->forget($propertyRawName);
} }
} }
} }
return $properties; return $properties;

View File

@ -11,6 +11,7 @@ use App\Dto\Concerns\HasLimitParameter;
use App\Dto\Concerns\HasPageParameter; use App\Dto\Concerns\HasPageParameter;
use App\Dto\Concerns\HasSfwParameter; use App\Dto\Concerns\HasSfwParameter;
use App\Dto\Concerns\HasUnapprovedParameter; use App\Dto\Concerns\HasUnapprovedParameter;
use App\Dto\Concerns\HasContinuingParameter;
use App\Enums\AnimeTypeEnum; use App\Enums\AnimeTypeEnum;
use App\Rules\Attributes\EnumValidation; use App\Rules\Attributes\EnumValidation;
use Spatie\LaravelData\Attributes\WithCast; use Spatie\LaravelData\Attributes\WithCast;
@ -20,7 +21,13 @@ use Spatie\LaravelData\Optional;
abstract class QueryAnimeSeasonCommand extends Data implements DataRequest 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)] #[WithCast(EnumCast::class, AnimeTypeEnum::class), EnumValidation(AnimeTypeEnum::class)]
public AnimeTypeEnum|Optional $filter; public AnimeTypeEnum|Optional $filter;

View File

@ -32,8 +32,6 @@ 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;
$season = $requestParams->has("season") ? $request->season : null;
$year = $requestParams->has("year") ? $request->year : null;
$results = $this->getSeasonItems($request, $type); $results = $this->getSeasonItems($request, $type);
// apply sfw, kids and unapproved filters // apply sfw, kids and unapproved filters
/** @noinspection PhpUndefinedMethodInspection */ /** @noinspection PhpUndefinedMethodInspection */

View File

@ -53,7 +53,8 @@ final class QueryCurrentAnimeSeasonHandler extends QueryAnimeSeasonHandlerBase
*/ */
[$from, $to] = $this->getSeasonRange($year, $season); [$from, $to] = $this->getSeasonRange($year, $season);
$premiered = ucfirst($season)." {$year}"; $premiered = ucfirst($season)." {$year}";
$includeContinuingItems = $request->continuing;
return $this->repository->getAiredBetween($from, $to, $type, $premiered); return $this->repository->getItemsBySeason($from, $to, $type, $premiered, $includeContinuingItems);
} }
} }

View File

@ -28,8 +28,8 @@ final class QuerySpecificAnimeSeasonHandler extends QueryAnimeSeasonHandlerBase
[$from, $to] = $this->getSeasonRange($request->year, $request->season); [$from, $to] = $this->getSeasonRange($request->year, $request->season);
$premiered = ucfirst($request->season)." {$request->year}"; $premiered = ucfirst($request->season)." {$request->year}";
$includeContinuingItems = $request->continuing;
return $this->repository->getAiredBetween($from, $to, $type, $premiered); return $this->repository->getItemsBySeason($from, $to, $type, $premiered, $includeContinuingItems);
// ->where("status", "!=", AnimeStatusEnum::upcoming()->label);
} }
} }

View File

@ -2,21 +2,12 @@
namespace App\Http\Controllers\V4DB; namespace App\Http\Controllers\V4DB;
use App\Anime;
use App\Dto\QueryAnimeSeasonListCommand; use App\Dto\QueryAnimeSeasonListCommand;
use App\Dto\QueryCurrentAnimeSeasonCommand; use App\Dto\QueryCurrentAnimeSeasonCommand;
use App\Dto\QuerySpecificAnimeSeasonCommand; use App\Dto\QuerySpecificAnimeSeasonCommand;
use App\Dto\QueryUpcomingAnimeSeasonCommand; 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 Exception;
use Illuminate\Http\Request; use OpenApi\Annotations as OA;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Jikan\Request\SeasonList\SeasonListRequest;
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
/** /**
* *
@ -38,6 +29,7 @@ class SeasonController extends Controller
* *
* @OA\Parameter(ref="#/components/parameters/sfw"), * @OA\Parameter(ref="#/components/parameters/sfw"),
* @OA\Parameter(ref="#/components/parameters/unapproved"), * @OA\Parameter(ref="#/components/parameters/unapproved"),
* @OA\Parameter(ref="#/components/parameters/continuing"),
* @OA\Parameter(ref="#/components/parameters/page"), * @OA\Parameter(ref="#/components/parameters/page"),
* @OA\Parameter(ref="#/components/parameters/limit"), * @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/sfw"),
* @OA\Parameter(ref="#/components/parameters/unapproved"), * @OA\Parameter(ref="#/components/parameters/unapproved"),
* @OA\Parameter(ref="#/components/parameters/continuing"),
* @OA\Parameter(ref="#/components/parameters/page"), * @OA\Parameter(ref="#/components/parameters/page"),
* @OA\Parameter(ref="#/components/parameters/limit"), * @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/sfw"),
* @OA\Parameter(ref="#/components/parameters/unapproved"), * @OA\Parameter(ref="#/components/parameters/unapproved"),
* @OA\Parameter(ref="#/components/parameters/continuing"),
* @OA\Parameter(ref="#/components/parameters/page"), * @OA\Parameter(ref="#/components/parameters/page"),
* @OA\Parameter(ref="#/components/parameters/limit"), * @OA\Parameter(ref="#/components/parameters/limit"),
* *

View File

@ -110,11 +110,12 @@ final class DefaultAnimeRepository extends DatabaseRepository implements AnimeRe
return $queryable; return $queryable;
} }
public function getAiredBetween( public function getItemsBySeason(
Carbon $from, Carbon $from,
Carbon $to, Carbon $to,
?AnimeTypeEnum $type = null, ?AnimeTypeEnum $type = null,
?string $premiered = null ?string $premiered = null,
bool $includeContinuingItems = false
): EloquentBuilder ): EloquentBuilder
{ {
$queryable = $this->queryable(true); $queryable = $this->queryable(true);
@ -127,9 +128,10 @@ final class DefaultAnimeRepository extends DatabaseRepository implements AnimeRe
$finalFilter = []; $finalFilter = [];
// if the premiered parameter for the filter is not null, look for those items which have a premiered attribute set, // 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. // 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) { if ($premiered !== null) {
$finalFilter['$or'] = [ $finalFilter['$or'] = [
['premiered' => $premiered], ['premiered' => $premiered],
@ -140,12 +142,14 @@ final class DefaultAnimeRepository extends DatabaseRepository implements AnimeRe
], ],
...$airedFilter ...$airedFilter
], ],
];
if ($includeContinuingItems) {
// this condition will include "continuing" items from previous seasons // this condition will include "continuing" items from previous seasons
[ $finalFilter['$or'][] = [
'aired.from' => ['$lte' => $from->toAtomString()], 'aired.from' => ['$lte' => $from->toAtomString()],
'airing' => true 'airing' => true
] ];
]; }
} else { } else {
$finalFilter = array_merge($finalFilter, $airedFilter); $finalFilter = array_merge($finalFilter, $airedFilter);
$finalFilter['aired.string'] = [ $finalFilter['aired.string'] = [

View File

@ -3046,6 +3046,9 @@
{ {
"$ref": "#/components/parameters/unapproved" "$ref": "#/components/parameters/unapproved"
}, },
{
"$ref": "#/components/parameters/continuing"
},
{ {
"$ref": "#/components/parameters/page" "$ref": "#/components/parameters/page"
}, },
@ -3115,6 +3118,9 @@
{ {
"$ref": "#/components/parameters/unapproved" "$ref": "#/components/parameters/unapproved"
}, },
{
"$ref": "#/components/parameters/continuing"
},
{ {
"$ref": "#/components/parameters/page" "$ref": "#/components/parameters/page"
}, },
@ -3191,6 +3197,9 @@
{ {
"$ref": "#/components/parameters/unapproved" "$ref": "#/components/parameters/unapproved"
}, },
{
"$ref": "#/components/parameters/continuing"
},
{ {
"$ref": "#/components/parameters/page" "$ref": "#/components/parameters/page"
}, },
@ -9027,6 +9036,15 @@
} }
}, },
"parameters": { "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 &#8243;TV (continuing)&#8243; section. (Example: https://myanimelist.net/anime/season/2024/winter) <br />Example usage: `?continuing`",
"required": false,
"schema": {
"type": "boolean"
}
},
"kids": { "kids": {
"name": "kids", "name": "kids",
"in": "query", "in": "query",
@ -9092,4 +9110,4 @@
"description": "About", "description": "About",
"url": "https://jikan.moe" "url": "https://jikan.moe"
} }
} }

View File

@ -131,9 +131,51 @@ class SeasonControllerTest extends TestCase
$state["airing"] = true; $state["airing"] = true;
$f->create($state); $f->create($state);
$content = $this->getJsonResponse([], "/v4/seasons/2024/winter"); $content = $this->getJsonResponse([], "/v4/seasons/2024/winter?continuing=true");
$this->seeStatusCode(200); $this->seeStatusCode(200);
$this->assertIsArray($content["data"]); $this->assertIsArray($content["data"]);
$this->assertCount(3, $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"]);
}
} }