Skip to content

Commit

Permalink
Merge pull request #527 from jikan-me/bugfix/continuing-items-in-season
Browse files Browse the repository at this point in the history
✅ Fixed a bug around season endpoints
  • Loading branch information
irfan-dahir committed Jun 27, 2024
2 parents 7dd9625 + cc38196 commit 9470beb
Show file tree
Hide file tree
Showing 14 changed files with 185 additions and 38 deletions.
5 changes: 3 additions & 2 deletions app/Contracts/AnimeRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,12 @@ public function getCurrentlyAiring(
?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;
Expand Down
26 changes: 26 additions & 0 deletions app/Dto/Concerns/HasContinuingParameter.php
Original file line number Diff line number Diff line change
@@ -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;
}
5 changes: 2 additions & 3 deletions app/Dto/Concerns/PreparesData.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ public static function prepareForPipeline(Collection $properties): Collection
{
// 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));
}
Expand All @@ -44,7 +43,7 @@ public static function prepareForPipeline(Collection $properties): Collection
}
}
// if the property is optional and the value is an empty string, we want to ignore it.
if ($property->type->isOptional && $propertyVal === "") {
if ($property->type->isOptional && $propertyVal === "" && !$property->type->acceptsType("bool")) {
$propertyVal = null;
}

Expand All @@ -53,7 +52,7 @@ public static function prepareForPipeline(Collection $properties): Collection
} else {
$properties->forget($propertyRawName);
}
}
}
}

return $properties;
Expand Down
9 changes: 8 additions & 1 deletion app/Dto/QueryAnimeSeasonCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
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;
Expand All @@ -20,7 +21,13 @@

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;
Expand Down
2 changes: 0 additions & 2 deletions app/Features/QueryAnimeSeasonHandlerBase.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,6 @@ public function handle($request): JsonResponse
{
$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 */
Expand Down
3 changes: 2 additions & 1 deletion app/Features/QueryCurrentAnimeSeasonHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ protected function getSeasonItems($request, ?AnimeTypeEnum $type): Builder
*/
[$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);
}
}
4 changes: 2 additions & 2 deletions app/Features/QuerySpecificAnimeSeasonHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ protected function getSeasonItems($request, ?AnimeTypeEnum $type): Builder

[$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);
}
}
14 changes: 4 additions & 10 deletions app/Http/Controllers/V4DB/SeasonController.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
*
Expand All @@ -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"),
*
Expand Down Expand Up @@ -89,6 +81,7 @@ public function now(QueryCurrentAnimeSeasonCommand $command)
*
* @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"),
*
Expand Down Expand Up @@ -177,6 +170,7 @@ public function archive(QueryAnimeSeasonListCommand $command)
*
* @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"),
*
Expand Down
57 changes: 49 additions & 8 deletions app/Repositories/DefaultAnimeRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Carbon;
use Laravel\Scout\Builder as ScoutBuilder;
use MongoDB\BSON\UTCDateTime;

/**
* @implements Repository<Anime>
Expand Down Expand Up @@ -110,11 +111,12 @@ public function getCurrentlyAiring(
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);
Expand All @@ -127,9 +129,10 @@ public function getAiredBetween(
$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],
Expand All @@ -140,12 +143,50 @@ public function getAiredBetween(
],
...$airedFilter
],
// this condition will include "continuing" items from previous seasons
[
];
if ($includeContinuingItems) {
// these conditions will include "continuing" items from previous seasons
// long running shows
$finalFilter['$or'][] = [
'aired.from' => ['$lte' => $from->toAtomString()],
'aired.to' => null,
'episodes' => null,
'airing' => true
]
];
];
// We want to include those which are currently airing, and their aired.to is past the date of the
// current season start.
$finalFilter['$or'][] = [
'aired.from' => ['$lte' => $from->toAtomString()],
'aired.to' => ['$gte' => $from->toAtomString()],
'airing' => true
];
// In many cases MAL doesn't show the date until an airing show is going to be aired. So we need to get
// clever here.
// We want to include those shows which have started in previous season only (not before) and it's going
// to continue in the current season.
$finalFilter['$or'][] = [
// note: this expression only works with mongodb version 5.0.0 or higher
'$expr' => [
'$lte' => [
[
'$dateDiff' => [
'startDate' => [
'$dateFromString' => [
'dateString' => '$aired.from'
]
],
'endDate' => new UTCDateTime($from),
'unit' => 'month'
]
],
3 // there are 3 months in a season, so anything that started in 3 months or less will be included
]
],
'aired.to' => null,
'episodes' => ['$gte' => 14],
'airing' => true
];
}
} else {
$finalFilter = array_merge($finalFilter, $airedFilter);
$finalFilter['aired.string'] = [
Expand Down
2 changes: 2 additions & 0 deletions app/Testing/SyntheticMongoDbTransaction.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public function beginDatabaseTransaction(): void
$tablesWithoutModels = [
"anime_characters_staff",
"anime_episodes",
"anime_episode",
"anime_forum",
"anime_moreinfo",
"anime_news",
Expand All @@ -42,6 +43,7 @@ public function beginDatabaseTransaction(): void
"anime_userupdates",
"anime_videos",
"character_pictures",
"characters_pictures",
"clubs_members",
"demographics_manga",
"demographics_anime",
Expand Down
12 changes: 6 additions & 6 deletions database/factories/JikanMediaModelFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ protected function getOverridesFromQueryStringParameters(Collection $additionalP
if ($additionalParams->has("start_date") && !empty($additionalParams["start_date"])
&& !$additionalParams->has("end_date")) {
$startDate = $this->adaptDateString($additionalParams["start_date"]);
$dt = Carbon::parse($startDate)->addDays($this->faker->numberBetween(0, 25));
$dt = Carbon::parse($startDate)->addDays($this->faker->numberBetween(1, 25));
$overrides[$activityMarkerKeyName] = new CarbonDateRange($dt, null);
}

Expand All @@ -253,7 +253,7 @@ protected function getOverridesFromQueryStringParameters(Collection $additionalP
$endDate = $this->adaptDateString($additionalParams["end_date"]);
$to = Carbon::parse($endDate);
$from = $to->copy()->subDays($this->faker->randomElement([30, 60, 90, 120, 180]));
$overrides[$activityMarkerKeyName] = new CarbonDateRange($from, $to->subDays($this->faker->numberBetween(0, 25)));
$overrides[$activityMarkerKeyName] = new CarbonDateRange($from, $to->subDays($this->faker->numberBetween(1, 25)));
}

if ($additionalParams->has(["start_date", "end_date"])
Expand Down Expand Up @@ -312,14 +312,14 @@ protected function getOppositeOverridesFromQueryStringParameters(Collection $add
if ($additionalParams->has("min_score") && !$additionalParams->has("max_score")) {
$min_score = floatval($additionalParams["min_score"]);
if ($this->isScoreValueValid($min_score)) {
$overrides["score"] = $this->faker->randomFloat(2, 1.00, floatval($additionalParams["min_score"]));
$overrides["score"] = $this->faker->randomFloat(2, 1.00, floatval($additionalParams["min_score"]) - 0.01);
}
}

if (!$additionalParams->has("min_score") && $additionalParams->has("max_score")) {
$max_score = $additionalParams["max_score"];
if ($this->isScoreValueValid($max_score)) {
$overrides["score"] = $this->faker->randomFloat(2, floatval($additionalParams["max_score"]), 9.99);
$overrides["score"] = $this->faker->randomFloat(2, floatval($additionalParams["max_score"]) + 0.01, 9.99);
}
}

Expand All @@ -330,8 +330,8 @@ protected function getOppositeOverridesFromQueryStringParameters(Collection $add
if ($this->isScoreValueValid($min_score) && $this->isScoreValueValid($max_score))
{
$overrides["score"] = $this->faker->randomElement([
$this->faker->randomFloat(2, 1.00, floatval($additionalParams["min_score"])),
$this->faker->randomFloat(2, floatval($additionalParams["max_score"]), 9.99)
$this->faker->randomFloat(2, 1.00, floatval($additionalParams["min_score"]) - 0.01),
$this->faker->randomFloat(2, floatval($additionalParams["max_score"]) + 0.01, 9.99)
]);
}
}
Expand Down
6 changes: 6 additions & 0 deletions database/factories/JikanModelFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,18 @@
use JMS\Serializer\Serializer;
use \Illuminate\Database\Eloquent\Factories\Factory;
use Spatie\Enum\Laravel\Faker\FakerEnumProvider;
use Illuminate\Support\Str;

abstract class JikanModelFactory extends Factory
{
public function configure(): JikanModelFactory|static
{
$this->faker->addProvider(new FakerEnumProvider($this->faker));
if (array_key_exists("GITHUB_JOB", $_ENV) && $_ENV["GITHUB_JOB"] !== "") {
$this->faker->seed($_ENV["GITHUB_JOB"]);
} else {
$this->faker->seed(Str::random());
}
return $this;
}

Expand Down
20 changes: 19 additions & 1 deletion storage/api-docs/api-docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -3046,6 +3046,9 @@
{
"$ref": "#/components/parameters/unapproved"
},
{
"$ref": "#/components/parameters/continuing"
},
{
"$ref": "#/components/parameters/page"
},
Expand Down Expand Up @@ -3115,6 +3118,9 @@
{
"$ref": "#/components/parameters/unapproved"
},
{
"$ref": "#/components/parameters/continuing"
},
{
"$ref": "#/components/parameters/page"
},
Expand Down Expand Up @@ -3191,6 +3197,9 @@
{
"$ref": "#/components/parameters/unapproved"
},
{
"$ref": "#/components/parameters/continuing"
},
{
"$ref": "#/components/parameters/page"
},
Expand Down Expand Up @@ -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 &#8243;TV (continuing)&#8243; section. (Example: https://myanimelist.net/anime/season/2024/winter) <br />Example usage: `?continuing`",
"required": false,
"schema": {
"type": "boolean"
}
},
"kids": {
"name": "kids",
"in": "query",
Expand Down Expand Up @@ -9092,4 +9110,4 @@
"description": "About",
"url": "https://jikan.moe"
}
}
}
Loading

0 comments on commit 9470beb

Please sign in to comment.