Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✅ Fixed a bug around season endpoints #527

Merged
merged 11 commits into from
Jun 27, 2024
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;
pushrbx marked this conversation as resolved.
Show resolved Hide resolved

#[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 @@
*/
[$from, $to] = $this->getSeasonRange($year, $season);
$premiered = ucfirst($season)." {$year}";
$includeContinuingItems = $request->continuing;

Check warning on line 56 in app/Features/QueryCurrentAnimeSeasonHandler.php

View check run for this annotation

Codecov / codecov/patch

app/Features/QueryCurrentAnimeSeasonHandler.php#L56

Added line #L56 was not covered by tests

return $this->repository->getAiredBetween($from, $to, $type, $premiered);
return $this->repository->getItemsBySeason($from, $to, $type, $premiered, $includeContinuingItems);

Check warning on line 58 in app/Features/QueryCurrentAnimeSeasonHandler.php

View check run for this annotation

Codecov / codecov/patch

app/Features/QueryCurrentAnimeSeasonHandler.php#L58

Added line #L58 was not covered by tests
}
}
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