diff --git a/.github/workflows/container-image-release.yml b/.github/workflows/container-image-release.yml
index bc73b12..72e835b 100644
--- a/.github/workflows/container-image-release.yml
+++ b/.github/workflows/container-image-release.yml
@@ -69,7 +69,9 @@ jobs:
- name: Build and push by digest
id: build
- uses: docker/build-push-action@v5
+ uses: docker/build-push-action@v6
+ env:
+ DOCKER_BUILD_NO_SUMMARY: true
with:
context: .
platforms: ${{ matrix.platform }}
diff --git a/COMMANDS.MD b/COMMANDS.MD
index b0656eb..dc050be 100644
--- a/COMMANDS.MD
+++ b/COMMANDS.MD
@@ -14,14 +14,15 @@ For an entire list of commands, you can run `php artisan list`
- [Indexer](#indexer)
- [Anime](#indexer-anime)
- [Manga](#indexer-manga)
-
+ - [Incremental](#indexer-incremental)
+
## Commands
### Serve
Command: `serve`
Example: `php artisan serve`
-Serve the application on the PHP development server
+Serve the application on the PHP development server
### Queue
@@ -66,7 +67,7 @@ Example: `cache:method queue`
Since v4 uses MongoDB as a means to index cache on some endpoints, having a built cache is important since it
works best for endpoints like search or top.
-`Indexer:Anime` uses [https://github.com/seanbreckenridge/mal-id-cache](https://github.com/seanbreckenridge/mal-id-cache) to fetch available MAL IDs and indexes them.
+`Indexer:Anime` uses [https://github.com/purarue/mal-id-cache](https://github.com/purarue/mal-id-cache) to fetch available MAL IDs and indexes them.
This function only needs to be run once. Any entry's cache updating will automatically be taken care of if it's expired, and a client makes a request for that entry.
@@ -90,7 +91,7 @@ This translates to running entries that previously failed to index or update, in
Since v4 uses MongoDB as a means to index cache on some endpoints, having a built cache is important since it
works best for endpoints like search or top.
-`Indexer:Manga` uses [https://github.com/seanbreckenridge/mal-id-cache](https://github.com/seanbreckenridge/mal-id-cache) to fetch available MAL IDs and indexes them.
+`Indexer:Manga` uses [https://github.com/purarue/mal-id-cache](https://github.com/purarue/mal-id-cache) to fetch available MAL IDs and indexes them.
This function only needs to be run once. Any entry's cache updating will automatically be taken care of if it's expired, and a client makes a request for that entry.
@@ -98,7 +99,7 @@ This function only needs to be run once. Any entry's cache updating will automat
Command:
```
-indexer:anime
+indexer:manga
{--failed : Run only entries that failed to index last time}
{--resume : Resume from the last position}
{--reverse : Start from the end of the array}
@@ -109,3 +110,16 @@ indexer:anime
Example: `indexer:manga`
This simply translates to running the indexer without any additional configuration.
+
+#### Indexer: Incremental
+Incrementally indexes media entries from MAL.
+This command will compare the latest version of MAL ids from the [mal_id_cache](https://github.com/purarue/mal-id-cache)
+github repository and compares them with the downloaded ids from the previous run. If no ids found from the previous run, a full indexing session is started.
+
+Command:
+```
+indexer:incremental {mediaType*}
+ {--failed : Run only entries that failed to index last time}
+ {--resume : Resume from the last position}
+ {--delay=3 : Set a delay between requests}
+```
diff --git a/README.MD b/README.MD
index 5ede423..4d3f2e8 100644
--- a/README.MD
+++ b/README.MD
@@ -52,7 +52,6 @@ For any additional help, join our [Discord server](http://discord.jikan.moe/).
| TypeScript | [jikants](https://github.com/Julien-Broyard/jikants) by Julien Broyard
[jikan-client](https://github.com/javi11/jikan-client) by Javier Blanco
š **(v4)** [jikan-ts](https://github.com/tutkli/jikan-ts) by Clara Castillo |
| PHP | [jikan-php](https://github.com/janvernieuwe/jikan-jikanPHP) by Jan Vernieuwe |
| .NET | š **(v4)** [Jikan.net](https://github.com/Ervie/jikan.net) by Ervie |
-| Elixir | [JikanEx](https://github.com/seanbreckenridge/jikan_ex) by Sean Breckenridge |
| Go | š **(v4)** [jikan-go](https://github.com/darenliang/jikan-go) by Daren Liang
[jikan2go](https://github.com/nokusukun/jikan2go) by nokusukun |
| Ruby | [Jikan.rb](https://github.com/Zerocchi/jikan.rb) by Zerocchi |
| Dart | [jikan-dart](https://github.com/charafau/jikan-dart) by Rafal Wachol |
diff --git a/app/Console/Commands/Indexer/AnimeIndexer.php b/app/Console/Commands/Indexer/AnimeIndexer.php
index 3e03f30..8704fea 100644
--- a/app/Console/Commands/Indexer/AnimeIndexer.php
+++ b/app/Console/Commands/Indexer/AnimeIndexer.php
@@ -2,7 +2,6 @@
namespace App\Console\Commands\Indexer;
-use App\Exceptions\Console\CommandAlreadyRunningException;
use App\Exceptions\Console\FileNotFoundException;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Storage;
@@ -67,7 +66,7 @@ class AnimeIndexer extends Command
$index = (int)$index;
$delay = (int)$delay;
- $this->info("Info: AnimeIndexer uses seanbreckenridge/mal-id-cache fetch available MAL IDs and updates/indexes them\n\n");
+ $this->info("Info: AnimeIndexer uses purarue/mal-id-cache fetch available MAL IDs and updates/indexes them\n\n");
if ($failed && Storage::exists('indexer/indexer_anime.failed')) {
$this->ids = $this->loadFailedMalIds();
@@ -140,14 +139,14 @@ class AnimeIndexer extends Command
/**
* @return array
- * @url https://github.com/seanbreckenridge/mal-id-cache
+ * @url https://github.com/purarue/mal-id-cache
*/
private function fetchMalIds() : array
{
- $this->info("Fetching MAL ID Cache https://raw.githubusercontent.com/seanbreckenridge/mal-id-cache/master/cache/anime_cache.json...\n");
+ $this->info("Fetching MAL ID Cache https://raw.githubusercontent.com/purarue/mal-id-cache/master/cache/anime_cache.json...\n");
$ids = json_decode(
- file_get_contents('https://raw.githubusercontent.com/seanbreckenridge/mal-id-cache/master/cache/anime_cache.json'),
+ file_get_contents('https://raw.githubusercontent.com/purarue/mal-id-cache/master/cache/anime_cache.json'),
true
);
diff --git a/app/Console/Commands/Indexer/AnimeSweepIndexer.php b/app/Console/Commands/Indexer/AnimeSweepIndexer.php
index ebfdffe..f29954e 100644
--- a/app/Console/Commands/Indexer/AnimeSweepIndexer.php
+++ b/app/Console/Commands/Indexer/AnimeSweepIndexer.php
@@ -74,14 +74,14 @@ class AnimeSweepIndexer extends Command
/**
* @return array
- * @url https://github.com/seanbreckenridge/mal-id-cache
+ * @url https://github.com/purarue/mal-id-cache
*/
private function fetchMalIds(): array
{
- $this->info("Fetching MAL ID Cache https://raw.githubusercontent.com/seanbreckenridge/mal-id-cache/master/cache/anime_cache.json...\n");
+ $this->info("Fetching MAL ID Cache https://raw.githubusercontent.com/purarue/mal-id-cache/master/cache/anime_cache.json...\n");
$ids = json_decode(
- file_get_contents('https://raw.githubusercontent.com/seanbreckenridge/mal-id-cache/master/cache/anime_cache.json'),
+ file_get_contents('https://raw.githubusercontent.com/purarue/mal-id-cache/master/cache/anime_cache.json'),
true
);
diff --git a/app/Console/Commands/Indexer/IncrementalIndexer.php b/app/Console/Commands/Indexer/IncrementalIndexer.php
new file mode 100644
index 0000000..eb076ac
--- /dev/null
+++ b/app/Console/Commands/Indexer/IncrementalIndexer.php
@@ -0,0 +1,228 @@
+ ['The media type to index.', 'Valid values: anime, manga']
+ ];
+ }
+
+ private function getExistingIds(string $mediaType): array
+ {
+ $existingIdsHash = "";
+ $existingIdsRaw = "";
+
+ if (Storage::exists("indexer/incremental/$mediaType.json"))
+ {
+ $existingIdsRaw = Storage::get("indexer/incremental/$mediaType.json");
+ $existingIdsHash = sha1($existingIdsRaw);
+ }
+
+ return [$existingIdsHash, $existingIdsRaw];
+ }
+
+ private function getIdsToFetch(string $mediaType): array
+ {
+ $idsToFetch = [];
+ [$existingIdsHash, $existingIdsRaw] = $this->getExistingIds($mediaType);
+
+ if ($this->cancelled)
+ {
+ return [];
+ }
+
+ $newIdsRaw = file_get_contents("https://raw.githubusercontent.com/purarue/mal-id-cache/master/cache/${mediaType}_cache.json");
+ $newIdsHash = sha1($newIdsRaw);
+
+ /** @noinspection PhpConditionAlreadyCheckedInspection */
+ if ($this->cancelled)
+ {
+ return [];
+ }
+
+ if ($newIdsHash !== $existingIdsHash)
+ {
+ $newIds = json_decode($newIdsRaw, true);
+ $existingIds = json_decode($existingIdsRaw, true);
+
+ if (is_null($existingIds) || count($existingIds) === 0)
+ {
+ $idsToFetch = $newIds;
+ }
+ else
+ {
+ foreach (["sfw", "nsfw"] as $t)
+ {
+ $idsToFetch[$t] = array_diff($existingIds[$t], $newIds[$t]);
+ }
+ }
+
+ Storage::put("indexer/incremental/$mediaType.json.tmp", $newIdsRaw);
+ }
+
+ return $idsToFetch;
+ }
+
+ private function getFailedIdsToFetch(string $mediaType): array
+ {
+ return json_decode(Storage::get("indexer/incremental/{$mediaType}_failed.json"));
+ }
+
+ private function fetchIds(string $mediaType, array $idsToFetch, bool $resume): void
+ {
+ $index = 0;
+ $success = [];
+ $failedIds = [];
+ $idCount = count($idsToFetch);
+ if ($resume && Storage::exists("indexer/incremental/{$mediaType}_resume.save"))
+ {
+ $index = (int)Storage::get("indexer/incremental/{$mediaType}_resume.save");
+ $this->info("Resuming from index: $index");
+ }
+
+ $ids = array_merge($idsToFetch['sfw'], $idsToFetch['nsfw']);
+
+ if ($index > 0 && !isset($ids[$index]))
+ {
+ $index = 0;
+ $this->warn('Invalid index; set back to 0');
+ }
+
+ Storage::put("indexer/incremental/{$mediaType}_resume.save", 0);
+
+ $this->info("$idCount $mediaType entries available");
+
+ for ($i = $index; $i <= ($idCount - 1); $i++)
+ {
+ if ($this->cancelled)
+ {
+ return;
+ }
+
+ $id = $ids[$index];
+
+ $url = env('APP_URL') . "/v4/$mediaType/$id";
+ $this->info("Indexing/Updating " . ($i + 1) . "/$idCount $url [MAL ID: $id]");
+
+ try
+ {
+ $response = json_decode(file_get_contents($url), true);
+ if (!isset($response['error']) || $response['status'] == 404)
+ {
+ continue;
+ }
+
+ $this->error("[SKIPPED] Failed to fetch $url - {$response['error']}");
+ }
+ catch (\Exception)
+ {
+ $this->warn("[SKIPPED] Failed to fetch $url");
+ $failedIds[] = $id;
+ Storage::put("indexer/incremental/$mediaType.failed", json_encode($failedIds));
+ }
+
+ $success[] = $id;
+ Storage::put("indexer/incremental/{$mediaType}_resume.save", $index);
+ }
+
+ Storage::delete("indexer/incremental/{$mediaType}_resume.save");
+
+ $this->info("--- Indexing of $mediaType is complete.");
+ $this->info(count($success) . ' entries indexed or updated.');
+ if (count($failedIds) > 0)
+ {
+ $this->info(count($failedIds) . ' entries failed to index or update. Re-run with --failed to requeue failed entries only.');
+ }
+
+ // finalize the latest state
+ Storage::move("indexer/incremental/$mediaType.json.tmp", "indexer/incremental/$mediaType.json");
+ }
+
+ public function handle(): int
+ {
+ // validate inputs
+ $validator = Validator::make(
+ [
+ 'mediaType' => $this->argument('mediaType'),
+ 'delay' => $this->option('delay'),
+ 'resume' => $this->option('resume') ?? false,
+ 'failed' => $this->option('failed') ?? false
+ ],
+ [
+ 'mediaType' => 'required|in:anime,manga',
+ 'delay' => 'integer|min:1',
+ 'resume' => 'bool|prohibited_with:failed',
+ 'failed' => 'bool|prohibited_with:resume'
+ ]
+ );
+
+ if ($validator->fails()) {
+ $this->error($validator->errors()->toJson());
+ return 1;
+ }
+
+ // we want to handle signals from the OS
+ $this->trap([SIGTERM, SIGQUIT, SIGINT], fn () => $this->cancelled = true);
+
+ $resume = $this->option('resume') ?? false;
+ $onlyFailed = $this->option('failed') ?? false;
+
+ /**
+ * @var $mediaTypes array
+ */
+ $mediaTypes = $this->argument("mediaType");
+
+ foreach ($mediaTypes as $mediaType)
+ {
+ $idsToFetch = [];
+
+ // if "--failed" option is specified just run the failed ones
+ if ($onlyFailed && Storage::exists("indexer/incremental/{$mediaType}_failed.json"))
+ {
+ $idsToFetch["sfw"] = $this->getFailedIdsToFetch($mediaType);
+ }
+ else
+ {
+ $idsToFetch = $this->getIdsToFetch($mediaType);
+ }
+
+ if ($this->cancelled)
+ {
+ return 127;
+ }
+
+ $idCount = count($idsToFetch);
+ if ($idCount === 0)
+ {
+ continue;
+ }
+
+ $this->fetchIds($mediaType, $idsToFetch, $resume);
+ }
+
+ return 0;
+ }
+}
diff --git a/app/Console/Commands/Indexer/MangaIndexer.php b/app/Console/Commands/Indexer/MangaIndexer.php
index a3ccefd..12d4f67 100644
--- a/app/Console/Commands/Indexer/MangaIndexer.php
+++ b/app/Console/Commands/Indexer/MangaIndexer.php
@@ -67,7 +67,7 @@ class MangaIndexer extends Command
$index = (int)$index;
$delay = (int)$delay;
- $this->info("Info: MangaIndexer uses seanbreckenridge/mal-id-cache fetch available MAL IDs and updates/indexes them\n\n");
+ $this->info("Info: MangaIndexer uses purarue/mal-id-cache fetch available MAL IDs and updates/indexes them\n\n");
if ($failed && Storage::exists('indexer/indexer_manga.failed')) {
$this->ids = $this->loadFailedMalIds();
@@ -140,14 +140,14 @@ class MangaIndexer extends Command
/**
* @return array
- * @url https://github.com/seanbreckenridge/mal-id-cache
+ * @url https://github.com/purarue/mal-id-cache
*/
private function fetchMalIds() : array
{
- $this->info("Fetching MAL ID Cache https://raw.githubusercontent.com/seanbreckenridge/mal-id-cache/master/cache/manga_cache.json...\n");
+ $this->info("Fetching MAL ID Cache https://raw.githubusercontent.com/purarue/mal-id-cache/master/cache/manga_cache.json...\n");
$ids = json_decode(
- file_get_contents('https://raw.githubusercontent.com/seanbreckenridge/mal-id-cache/master/cache/manga_cache.json'),
+ file_get_contents('https://raw.githubusercontent.com/purarue/mal-id-cache/master/cache/manga_cache.json'),
true
);
diff --git a/app/Console/Commands/Indexer/MangaSweepIndexer.php b/app/Console/Commands/Indexer/MangaSweepIndexer.php
index 09dfdb3..1f45e3e 100644
--- a/app/Console/Commands/Indexer/MangaSweepIndexer.php
+++ b/app/Console/Commands/Indexer/MangaSweepIndexer.php
@@ -71,14 +71,14 @@ class MangaSweepIndexer extends Command
/**
* @return array
- * @url https://github.com/seanbreckenridge/mal-id-cache
+ * @url https://github.com/purarue/mal-id-cache
*/
private function fetchMalIds(): array
{
- $this->info("Fetching MAL ID Cache https://raw.githubusercontent.com/seanbreckenridge/mal-id-cache/master/cache/manga_cache.json...\n");
+ $this->info("Fetching MAL ID Cache https://raw.githubusercontent.com/purarue/mal-id-cache/master/cache/manga_cache.json...\n");
$ids = json_decode(
- file_get_contents('https://raw.githubusercontent.com/seanbreckenridge/mal-id-cache/master/cache/manga_cache.json'),
+ file_get_contents('https://raw.githubusercontent.com/purarue/mal-id-cache/master/cache/manga_cache.json'),
true
);
diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php
index 22c9b19..eafe555 100644
--- a/app/Console/Kernel.php
+++ b/app/Console/Kernel.php
@@ -24,7 +24,8 @@ class Kernel extends ConsoleKernel
Indexer\GenreIndexer::class,
Indexer\ProducersIndexer::class,
Indexer\AnimeSweepIndexer::class,
- Indexer\MangaSweepIndexer::class
+ Indexer\MangaSweepIndexer::class,
+ Indexer\IncrementalIndexer::class
];
/**
diff --git a/app/Dto/Concerns/HasSpoilersParameter.php b/app/Dto/Concerns/HasSpoilersParameter.php
index b704e46..7ba499c 100644
--- a/app/Dto/Concerns/HasSpoilersParameter.php
+++ b/app/Dto/Concerns/HasSpoilersParameter.php
@@ -10,7 +10,7 @@ use Spatie\LaravelData\Optional;
/**
* @OA\Parameter(
- * name="spoiler",
+ * name="spoilers",
* in="query",
* required=false,
* description="Any reviews that are tagged as a spoiler. Spoiler reviews are not returned by default. e.g usage: `?spoiler=true`",
diff --git a/app/Dto/LookupDataCommand.php b/app/Dto/LookupDataCommand.php
index 5949d97..9d60f2d 100644
--- a/app/Dto/LookupDataCommand.php
+++ b/app/Dto/LookupDataCommand.php
@@ -4,7 +4,6 @@ namespace App\Dto;
use App\Concerns\HasRequestFingerprint;
use App\Contracts\DataRequest;
-use App\DataPipes\MapRouteParametersDataPipe;
use App\Dto\Concerns\MapsRouteParameters;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Http\Resources\Json\ResourceCollection;
@@ -13,12 +12,6 @@ use Spatie\LaravelData\Attributes\Validation\Min;
use Spatie\LaravelData\Attributes\Validation\Numeric;
use Spatie\LaravelData\Attributes\Validation\Required;
use Spatie\LaravelData\Data;
-use Spatie\LaravelData\DataPipeline;
-use Spatie\LaravelData\DataPipes\AuthorizedDataPipe;
-use Spatie\LaravelData\DataPipes\CastPropertiesDataPipe;
-use Spatie\LaravelData\DataPipes\DefaultValuesDataPipe;
-use Spatie\LaravelData\DataPipes\MapPropertiesDataPipe;
-use Spatie\LaravelData\DataPipes\ValidatePropertiesDataPipe;
/**
* Base class for all requests/commands which are for looking up things by id.
diff --git a/app/Dto/QueryTopReviewsCommand.php b/app/Dto/QueryTopReviewsCommand.php
index b9ede8e..322b66f 100644
--- a/app/Dto/QueryTopReviewsCommand.php
+++ b/app/Dto/QueryTopReviewsCommand.php
@@ -2,14 +2,12 @@
namespace App\Dto;
-use App\Casts\ContextualBooleanCast;
use App\Casts\EnumCast;
use App\Concerns\HasRequestFingerprint;
use App\Contracts\DataRequest;
use App\Dto\Concerns\HasPreliminaryParameter;
use App\Dto\Concerns\HasSpoilersParameter;
use App\Dto\Concerns\PreparesData;
-use App\Enums\TopAnimeFilterEnum;
use App\Enums\TopReviewsTypeEnum;
use App\Rules\Attributes\EnumValidation;
use Illuminate\Http\JsonResponse;
@@ -23,6 +21,6 @@ final class QueryTopReviewsCommand extends QueryTopItemsCommand implements DataR
{
use HasRequestFingerprint, HasPreliminaryParameter, HasSpoilersParameter, PreparesData;
- #[WithCast(EnumCast::class, TopAnimeFilterEnum::class), EnumValidation(TopReviewsTypeEnum::class)]
+ #[WithCast(EnumCast::class, TopReviewsTypeEnum::class), EnumValidation(TopReviewsTypeEnum::class)]
public TopReviewsTypeEnum|Optional $type;
}
diff --git a/app/Features/QuerySpecificAnimeSeasonHandler.php b/app/Features/QuerySpecificAnimeSeasonHandler.php
index 1ec6821..e073fa0 100644
--- a/app/Features/QuerySpecificAnimeSeasonHandler.php
+++ b/app/Features/QuerySpecificAnimeSeasonHandler.php
@@ -3,8 +3,6 @@
namespace App\Features;
use App\Dto\QuerySpecificAnimeSeasonCommand;
-use App\Enums\AnimeSeasonEnum;
-use App\Enums\AnimeStatusEnum;
use App\Enums\AnimeTypeEnum;
use Illuminate\Contracts\Database\Query\Builder;
use Illuminate\Support\Carbon;
diff --git a/app/Features/QueryTopReviewsHandler.php b/app/Features/QueryTopReviewsHandler.php
index 6eaae8b..a967d99 100644
--- a/app/Features/QueryTopReviewsHandler.php
+++ b/app/Features/QueryTopReviewsHandler.php
@@ -7,7 +7,6 @@ use App\Enums\TopReviewsTypeEnum;
use App\Support\CachedData;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Collection;
-use Jikan\Helper\Constants;
use Jikan\MyAnimeList\MalClient;
use Jikan\Request\Reviews\ReviewsRequest;
@@ -31,7 +30,7 @@ final class QueryTopReviewsHandler extends RequestHandlerWithScraperCache
$preliminary = $requestParams->get("preliminary", true);
return $this->scraperService->findList(
$requestFingerPrint,
- fn (MalClient $jikan, ?int $page = null) => $jikan->getReviews(new ReviewsRequest($type, $page, $spoilers, $preliminary)),
+ fn (MalClient $jikan, ?int $page = null) => $jikan->getReviews(new ReviewsRequest(ensureEnumPrimitiveValue($type), $page, $spoilers, $preliminary)),
$requestParams->get("page"));
}
}
diff --git a/app/Http/Controllers/V4DB/AnimeController.php b/app/Http/Controllers/V4DB/AnimeController.php
index b6c26af..abdd98d 100644
--- a/app/Http/Controllers/V4DB/AnimeController.php
+++ b/app/Http/Controllers/V4DB/AnimeController.php
@@ -22,7 +22,7 @@ use App\Dto\AnimeThemesLookupCommand;
use App\Dto\AnimeUserUpdatesLookupCommand;
use App\Dto\AnimeVideosEpisodesLookupCommand;
use App\Dto\AnimeVideosLookupCommand;
-use Illuminate\Http\Request;
+use OpenApi\Annotations as OA;
class AnimeController extends Controller
{
@@ -225,15 +225,17 @@ class AnimeController extends Controller
* nullable=true
* ),
* @OA\Property(
- * property="duration",
- * type="integer",
- * description="Episode duration in seconds",
- * nullable=true
- * ),
- * @OA\Property(
* property="aired",
* type="string",
* description="Aired Date ISO8601",
+ * nullable=true
+ * ),
+ * @OA\Property(
+ * property="score",
+ * type="float",
+ * description="Aggregated episode score (1.00 - 5.00) based on MyAnimeList user voting",
+* minimum="1",
+* maximum="5",
* nullable=true
* ),
* @OA\Property(
@@ -669,7 +671,7 @@ class AnimeController extends Controller
*
* @OA\Parameter(ref="#/components/parameters/page"),
* @OA\Parameter(ref="#/components/parameters/preliminary"),
- * @OA\Parameter(ref="#/components/parameters/spoiler"),
+ * @OA\Parameter(ref="#/components/parameters/spoilers"),
*
* @OA\Response(
* response="200",
diff --git a/app/Http/Controllers/V4DB/MangaController.php b/app/Http/Controllers/V4DB/MangaController.php
index e1794eb..f863a2e 100644
--- a/app/Http/Controllers/V4DB/MangaController.php
+++ b/app/Http/Controllers/V4DB/MangaController.php
@@ -15,7 +15,7 @@ use App\Dto\MangaRelationsLookupCommand;
use App\Dto\MangaReviewsLookupCommand;
use App\Dto\MangaStatsLookupCommand;
use App\Dto\MangaUserUpdatesLookupCommand;
-use Illuminate\Http\Request;
+use OpenApi\Annotations as OA;
class MangaController extends Controller
{
@@ -387,7 +387,7 @@ class MangaController extends Controller
*
* @OA\Parameter(ref="#/components/parameters/page"),
* @OA\Parameter(ref="#/components/parameters/preliminary"),
- * @OA\Parameter(ref="#/components/parameters/spoiler"),
+ * @OA\Parameter(ref="#/components/parameters/spoilers"),
*
* @OA\Response(
* response="200",
diff --git a/app/Http/Controllers/V4DB/ReviewsController.php b/app/Http/Controllers/V4DB/ReviewsController.php
index dcf8ac2..8dca6a9 100644
--- a/app/Http/Controllers/V4DB/ReviewsController.php
+++ b/app/Http/Controllers/V4DB/ReviewsController.php
@@ -4,18 +4,20 @@ namespace App\Http\Controllers\V4DB;
use App\Dto\QueryAnimeReviewsCommand;
use App\Dto\QueryMangaReviewsCommand;
+use OpenApi\Annotations as OA;
class ReviewsController extends Controller
{
/**
- * @OA\Get(
+ * @OA\
+ * Get(
* path="/reviews/anime",
* operationId="getRecentAnimeReviews",
* tags={"reviews"},
*
* @OA\Parameter(ref="#/components/parameters/page"),
* @OA\Parameter(ref="#/components/parameters/preliminary"),
- * @OA\Parameter(ref="#/components/parameters/spoiler"),
+ * @OA\Parameter(ref="#/components/parameters/spoilers"),
*
*
* @OA\Response(
@@ -70,7 +72,7 @@ class ReviewsController extends Controller
*
* @OA\Parameter(ref="#/components/parameters/page"),
* @OA\Parameter(ref="#/components/parameters/preliminary"),
- * @OA\Parameter(ref="#/components/parameters/spoiler"),
+ * @OA\Parameter(ref="#/components/parameters/spoilers"),
*
* @OA\Response(
* response="200",
diff --git a/app/Http/Resources/V4/ReviewsResource.php b/app/Http/Resources/V4/ReviewsResource.php
index 256beb2..0bca9e0 100644
--- a/app/Http/Resources/V4/ReviewsResource.php
+++ b/app/Http/Resources/V4/ReviewsResource.php
@@ -3,6 +3,7 @@
namespace App\Http\Resources\V4;
use Illuminate\Http\Resources\Json\JsonResource;
+use OpenApi\Annotations as OA;
class ReviewsResource extends JsonResource
{
diff --git a/app/Producers.php b/app/Producers.php
index f46216d..dd5441c 100644
--- a/app/Producers.php
+++ b/app/Producers.php
@@ -72,6 +72,33 @@ class Producers extends JikanApiSearchableModel
];
}
+ public function getCollectionSchema(): array
+ {
+ return [
+ 'name' => $this->searchableAs(),
+ 'fields' => [
+ [
+ 'name' => '.*',
+ 'type' => 'auto',
+ ],
+ [
+ 'name' => 'titles',
+ 'type' => 'string',
+ 'optional' => false,
+ 'infix' => true,
+ 'sort' => true
+ ],
+ [
+ 'name' => 'url',
+ 'type' => 'string',
+ 'optional' => false,
+ 'infix' => true,
+ 'sort' => true
+ ],
+ ]
+ ];
+ }
+
public function typesenseQueryBy(): array
{
return [
diff --git a/app/Repositories/DefaultAnimeRepository.php b/app/Repositories/DefaultAnimeRepository.php
index ffbf103..9843629 100644
--- a/app/Repositories/DefaultAnimeRepository.php
+++ b/app/Repositories/DefaultAnimeRepository.php
@@ -167,19 +167,39 @@ final class DefaultAnimeRepository extends DatabaseRepository implements AnimeRe
$finalFilter['$or'][] = [
// note: this expression only works with mongodb version 5.0.0 or higher
'$expr' => [
- '$lte' => [
+ '$and' => [
[
- '$dateDiff' => [
- 'startDate' => [
- '$dateFromString' => [
- 'dateString' => '$aired.from'
+ '$lte' => [
+ [
+ '$dateDiff' => [
+ 'startDate' => [
+ '$dateFromString' => [
+ 'dateString' => '$aired.from'
+ ]
+ ],
+ 'endDate' => new UTCDateTime($from),
+ 'unit' => 'month'
]
],
- '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
+ ],
],
- 3 // there are 3 months in a season, so anything that started in 3 months or less will be included
+ [
+ '$gt' => [
+ [
+ '$dateDiff' => [
+ 'startDate' => [
+ '$dateFromString' => [
+ 'dateString' => '$aired.from'
+ ]
+ ],
+ 'endDate' => new UTCDateTime($from),
+ 'unit' => 'month'
+ ]
+ ],
+ 0
+ ]
+ ]
]
],
'aired.to' => null,
diff --git a/app/Support/helpers.php b/app/Support/helpers.php
index dc9094d..df69542 100644
--- a/app/Support/helpers.php
+++ b/app/Support/helpers.php
@@ -114,3 +114,12 @@ if (! function_exists('cache')) {
return app('cache')->put(key($arguments[0]), reset($arguments[0]), $arguments[1] ?? null);
}
}
+
+if (!function_exists("ensureEnumPrimitiveValue")) {
+ function ensureEnumPrimitiveValue(int|string|bool|float|null|\Spatie\Enum\Laravel\Enum $value): mixed {
+ if ($value instanceof \Spatie\Enum\Laravel\Enum) {
+ return $value->value;
+ }
+ return $value;
+ }
+}
diff --git a/composer.json b/composer.json
index 8234697..55e6a70 100644
--- a/composer.json
+++ b/composer.json
@@ -14,6 +14,7 @@
"php": "^8.1",
"ext-json": "*",
"ext-mongodb": "*",
+ "ext-pcntl": "*",
"amphp/http-client": "^4.6",
"danielmewes/php-rql": "dev-master",
"darkaonline/swagger-lume": "^9.0",
diff --git a/composer.lock b/composer.lock
index b7d9f1a..572b6df 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4554,16 +4554,16 @@
},
{
"name": "jikan-me/jikan",
- "version": "v4.0.11",
+ "version": "v4.0.12",
"source": {
"type": "git",
"url": "https://github.com/jikan-me/jikan.git",
- "reference": "fcc8d20817ce29332b496a21652b9965c6c8196c"
+ "reference": "dcb47237a9407473f484bd28a5e44479cdd916fc"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/jikan-me/jikan/zipball/fcc8d20817ce29332b496a21652b9965c6c8196c",
- "reference": "fcc8d20817ce29332b496a21652b9965c6c8196c",
+ "url": "https://api.github.com/repos/jikan-me/jikan/zipball/dcb47237a9407473f484bd28a5e44479cdd916fc",
+ "reference": "dcb47237a9407473f484bd28a5e44479cdd916fc",
"shasum": ""
},
"require": {
@@ -4602,7 +4602,7 @@
"description": "Jikan is an unofficial MyAnimeList API",
"support": {
"issues": "https://github.com/jikan-me/jikan/issues",
- "source": "https://github.com/jikan-me/jikan/tree/v4.0.11"
+ "source": "https://github.com/jikan-me/jikan/tree/v4.0.12"
},
"funding": [
{
@@ -4610,7 +4610,7 @@
"type": "patreon"
}
],
- "time": "2024-05-30T08:15:50+00:00"
+ "time": "2024-09-20T22:15:42+00:00"
},
{
"name": "jms/metadata",
@@ -13591,5 +13591,5 @@
"ext-mongodb": "*"
},
"platform-dev": [],
- "plugin-api-version": "2.6.0"
+ "plugin-api-version": "2.3.0"
}
diff --git a/config/swagger-lume.php b/config/swagger-lume.php
index f9182e3..d5ffb8a 100644
--- a/config/swagger-lume.php
+++ b/config/swagger-lume.php
@@ -260,6 +260,7 @@ return [
| `BadRequestException` | `405 - Method Not Allowed` | Requested Method is not supported for resource. Only `GET` requests are allowed |
| `RateLimitException` | `429 - Too Many Request` | You are being rate limited by Jikan or MyAnimeList is rate-limiting our servers (specified in the error response) |
| `UpstreamException`,`ParserException`,etc. | `500 - Internal Server Error` | Something didn't work. Try again later. If you see an error response with a `report_url` URL, please click on it to open an auto-generated GitHub issue |
+ | `ServiceUnavailableException` | `503 - Service Unavailable` | In most cases this is intentionally done if the service is down for maintenance. |
## JSON Error Response
diff --git a/container-setup.sh b/container-setup.sh
index 89c39c6..3328af2 100755
--- a/container-setup.sh
+++ b/container-setup.sh
@@ -34,6 +34,7 @@ display_help() {
echo "stop Stop Jikan API"
echo "validate-prereqs Validate pre-reqs installed (docker, docker-compose)"
echo "execute-indexers Execute the indexers, which will scrape and index data from MAL. (Notice: This can take days)"
+ echo "index-incrementally Executes the incremental indexers for each media type. (anime, manga)"
echo ""
}
@@ -168,6 +169,11 @@ case "$1" in
$DOCKER_COMPOSE_CMD -p "$DOCKER_COMPOSE_PROJECT_NAME" exec jikan_rest php /app/artisan indexer:producers
echo "Indexing done!"
;;
+ "index-incrementally")
+ echo "Indexing..."
+ $DOCKER_COMPOSE_CMD -p "$DOCKER_COMPOSE_PROJECT_NAME" exec jikan_rest php /app/artisan indexer:incremental anime manga
+ echo "Indexing done!"
+ ;;
*)
echo "No command specified, displaying help"
display_help
diff --git a/container_usage.md b/container_usage.md
index bce6bd7..f20e110 100644
--- a/container_usage.md
+++ b/container_usage.md
@@ -16,6 +16,9 @@ This will:
> **Note**: The script supports both `docker` and `podman`. In case of `podman` please bare in mind that sometimes the container name resolution doesn't work on the container network.
> In those cases you might have to install `aardvark-dns` package. On `Arch Linux` podman uses `netavark` network by default (in 2023) so you will need to install the before mentioned package.
+> **Note 2**: The script will start the jikan API, but if you start it for the first time, it won't have any data in it!
+> You will have to run the indexers through artisan to have data. See ["Running the indexer with the script"](#running-the-indexer-with-the-script) section.
+
The script has the following prerequisites and will notify you if these are not present:
- git
@@ -36,6 +39,7 @@ start Start Jikan API (mongodb, typesense, redis, jikan-api wor
stop Stop Jikan API
validate-prereqs Validate pre-reqs installed (docker, docker-compose)
execute-indexers Execute the indexers, which will scrape and index data from MAL. (Notice: This can take days)
+index-incrementally Executes the incremental indexers for each media type. (anime, manga)
```
### Running the indexer with the script
diff --git a/docker-compose.yml b/docker-compose.yml
index e1c0843..edd17cd 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -23,7 +23,7 @@ secrets:
services:
jikan_rest:
- image: "jikanme/jikan-rest:${_JIKAN_API_VERSION:-latest}"
+ image: "docker.io/jikanme/jikan-rest:${_JIKAN_API_VERSION:-latest}"
user: "${APP_UID:-10001}:${APP_GID:-10001}"
networks:
- jikan_network
@@ -116,7 +116,3 @@ services:
- typesense-data:/data
ports:
- "8108/tcp"
- healthcheck:
- test: [ 'CMD-SHELL', '{ ! [ -f "curl_created" ] && apt -qq update -y && apt -qq install -y curl && touch curl_created && curl -s -f http://localhost:8108/health; } || { curl -s -f http://localhost:8108/health; }' ]
- interval: 5s
- timeout: 2s
diff --git a/storage/api-docs/api-docs.json b/storage/api-docs/api-docs.json
index 8ab520a..a8bc1b7 100644
--- a/storage/api-docs/api-docs.json
+++ b/storage/api-docs/api-docs.json
@@ -2,7 +2,7 @@
"openapi": "3.0.0",
"info": {
"title": "Jikan API",
- "description": "[Jikan](https://jikan.moe) is an **Unofficial** MyAnimeList API.\nIt scrapes the website to satisfy the need for a complete API - which MyAnimeList lacks.\n\n# Information\n\nā” Jikan is powered by its awesome backers - š [Become a backer](https://www.patreon.com/jikan)\n\n## Rate Limiting\n\n| Duration | Requests |\n|----|----|\n| Daily | **Unlimited** |\n| Per Minute | 60 requests |\n| Per Second | 3 requests |\n\nNote: It's still possible to get rate limited from MyAnimeList.net instead.\n\n\n## JSON Notes\n- Any property (except arrays or objects) whose value does not exist or is undetermined, will be `null`.\n- Any array or object property whose value does not exist or is undetermined, will be empty.\n- Any `score` property whose value does not exist or is undetermined, will be `0`.\n- All dates and timestamps are returned in [ISO8601](https://en.wikipedia.org/wiki/ISO_8601) format and in UTC timezone\n\n## Caching\nBy **CACHING**, we refer to the data parsed from MyAnimeList which is stored temporarily on our servers to provide better API performance.\n\nAll requests are cached for **24 hours**.\n\nThe following response headers will detail cache information.\n\n| Header | Remarks |\n| ---- | ---- |\n| `Expires` | Cache expiry date |\n| `Last-Modified` | Cache set date |\n| `X-Request-Fingerprint` | Unique request fingerprint (only for cachable requests, not queries) |\n\n\nNote: `X-Request-Fingerprint` will only be available on single resource requests and their child endpoints. e.g `/anime/1`, `/anime/1/relations`.\nThey won't be available on pages which perform queries, like /anime, or /top/anime, etc.\n\n## Allowed HTTP(s) requests\n\n**Jikan REST API does not provide authenticated requests for MyAnimeList.** This means you can not use it to update your anime/manga list.\nOnly GET requests are supported which return READ-ONLY data.\n\n## HTTP Responses\n\nAll error responses are accompanied by a JSON Error response.\n\n| Exception | HTTP Status | Remarks |\n| ---- | ---- | ---- |\n| N/A | `200 - OK` | The request was successful |\n| N/A | `304 - Not Modified` | You have the latest data (Cache Validation response) |\n| `BadRequestException`,`ValidationException` | `400 - Bad Request` | You've made an invalid request. Recheck documentation |\n| `BadResponseException` | `404 - Not Found` | The resource was not found or MyAnimeList responded with a `404` |\n| `BadRequestException` | `405 - Method Not Allowed` | Requested Method is not supported for resource. Only `GET` requests are allowed |\n| `RateLimitException` | `429 - Too Many Request` | You are being rate limited by Jikan or MyAnimeList is rate-limiting our servers (specified in the error response) |\n| `UpstreamException`,`ParserException`,etc. | `500 - Internal Server Error` | Something didn't work. Try again later. If you see an error response with a `report_url` URL, please click on it to open an auto-generated GitHub issue |\n\n## JSON Error Response\n\n```json\n {\n \"status\": 500,\n \"type\": \"InternalException\",\n \"message\": \"Exception Message\",\n \"error\": \"Exception Trace\",\n \"report_url\": \"https://github.com...\"\n }\n```\n\n| Property | Remarks |\n| ---- | ---- |\n| `status` | Returned HTTP Status Code |\n| `type` | Thrown Exception |\n| `message` | Human-readable error message |\n| `error` | Error response and trace from the API |\n| `report_url` | Clicking this would redirect you to a generated GitHub issue |\n\n\n## Cache Validation\n\n- All requests return a `ETag` header which is an MD5 hash of the response\n- You can use this hash to verify if there's new or updated content by suppliying it as the value for the `If-None-Match` in your next request header\n- You will get a HTTP `304 - Not Modified` response if the content has not changed\n- If the content has changed, you'll get a HTTP `200 - OK` response with the updated JSON response\n\n\n\n## Disclaimer\n\n- Jikan is not affiliated with MyAnimeList.net.\n- Jikan is a free, open-source API. Please use it responsibly.\n\n----\n\nBy using the API, you are agreeing to Jikan's [terms of use](https://jikan.moe/terms) policy.\n\n[v3 Documentation](https://jikan.docs.apiary.io/) - [Wrappers/SDKs](https://github.com/jikan-me/jikan#wrappers) - [Report an issue](https://github.com/jikan-me/jikan-rest/issues/new) - [Host your own server](https://github.com/jikan-me/jikan-rest)",
+ "description": "[Jikan](https://jikan.moe) is an **Unofficial** MyAnimeList API.\nIt scrapes the website to satisfy the need for a complete API - which MyAnimeList lacks.\n\n# Information\n\nā” Jikan is powered by its awesome backers - š [Become a backer](https://www.patreon.com/jikan)\n\n## Rate Limiting\n\n| Duration | Requests |\n|----|----|\n| Daily | **Unlimited** |\n| Per Minute | 60 requests |\n| Per Second | 3 requests |\n\nNote: It's still possible to get rate limited from MyAnimeList.net instead.\n\n\n## JSON Notes\n- Any property (except arrays or objects) whose value does not exist or is undetermined, will be `null`.\n- Any array or object property whose value does not exist or is undetermined, will be empty.\n- Any `score` property whose value does not exist or is undetermined, will be `0`.\n- All dates and timestamps are returned in [ISO8601](https://en.wikipedia.org/wiki/ISO_8601) format and in UTC timezone\n\n## Caching\nBy **CACHING**, we refer to the data parsed from MyAnimeList which is stored temporarily on our servers to provide better API performance.\n\nAll requests are cached for **24 hours**.\n\nThe following response headers will detail cache information.\n\n| Header | Remarks |\n| ---- | ---- |\n| `Expires` | Cache expiry date |\n| `Last-Modified` | Cache set date |\n| `X-Request-Fingerprint` | Unique request fingerprint (only for cachable requests, not queries) |\n\n\nNote: `X-Request-Fingerprint` will only be available on single resource requests and their child endpoints. e.g `/anime/1`, `/anime/1/relations`.\nThey won't be available on pages which perform queries, like /anime, or /top/anime, etc.\n\n## Allowed HTTP(s) requests\n\n**Jikan REST API does not provide authenticated requests for MyAnimeList.** This means you can not use it to update your anime/manga list.\nOnly GET requests are supported which return READ-ONLY data.\n\n## HTTP Responses\n\nAll error responses are accompanied by a JSON Error response.\n\n| Exception | HTTP Status | Remarks |\n| ---- | ---- | ---- |\n| N/A | `200 - OK` | The request was successful |\n| N/A | `304 - Not Modified` | You have the latest data (Cache Validation response) |\n| `BadRequestException`,`ValidationException` | `400 - Bad Request` | You've made an invalid request. Recheck documentation |\n| `BadResponseException` | `404 - Not Found` | The resource was not found or MyAnimeList responded with a `404` |\n| `BadRequestException` | `405 - Method Not Allowed` | Requested Method is not supported for resource. Only `GET` requests are allowed |\n| `RateLimitException` | `429 - Too Many Request` | You are being rate limited by Jikan or MyAnimeList is rate-limiting our servers (specified in the error response) |\n| `UpstreamException`,`ParserException`,etc. | `500 - Internal Server Error` | Something didn't work. Try again later. If you see an error response with a `report_url` URL, please click on it to open an auto-generated GitHub issue |\n| `ServiceUnavailableException` | `503 - Service Unavailable` | In most cases this is intentionally done if the service is down for maintenance. |\n\n## JSON Error Response\n\n```json\n {\n \"status\": 500,\n \"type\": \"InternalException\",\n \"message\": \"Exception Message\",\n \"error\": \"Exception Trace\",\n \"report_url\": \"https://github.com...\"\n }\n```\n\n| Property | Remarks |\n| ---- | ---- |\n| `status` | Returned HTTP Status Code |\n| `type` | Thrown Exception |\n| `message` | Human-readable error message |\n| `error` | Error response and trace from the API |\n| `report_url` | Clicking this would redirect you to a generated GitHub issue |\n\n\n## Cache Validation\n\n- All requests return a `ETag` header which is an MD5 hash of the response\n- You can use this hash to verify if there's new or updated content by suppliying it as the value for the `If-None-Match` in your next request header\n- You will get a HTTP `304 - Not Modified` response if the content has not changed\n- If the content has changed, you'll get a HTTP `200 - OK` response with the updated JSON response\n\n\n\n## Disclaimer\n\n- Jikan is not affiliated with MyAnimeList.net.\n- Jikan is a free, open-source API. Please use it responsibly.\n\n----\n\nBy using the API, you are agreeing to Jikan's [terms of use](https://jikan.moe/terms) policy.\n\n[v3 Documentation](https://jikan.docs.apiary.io/) - [Wrappers/SDKs](https://github.com/jikan-me/jikan#wrappers) - [Report an issue](https://github.com/jikan-me/jikan-rest/issues/new) - [Host your own server](https://github.com/jikan-me/jikan-rest)",
"termsOfService": "https://jikan.moe/terms",
"contact": {
"name": "API Support (Discord)",
@@ -587,7 +587,7 @@
"$ref": "#/components/parameters/preliminary"
},
{
- "$ref": "#/components/parameters/spoiler"
+ "$ref": "#/components/parameters/spoilers"
}
],
"responses": {
@@ -1605,7 +1605,7 @@
"$ref": "#/components/parameters/preliminary"
},
{
- "$ref": "#/components/parameters/spoiler"
+ "$ref": "#/components/parameters/spoilers"
}
],
"responses": {
@@ -2226,7 +2226,7 @@
"$ref": "#/components/parameters/preliminary"
},
{
- "$ref": "#/components/parameters/spoiler"
+ "$ref": "#/components/parameters/spoilers"
}
],
"responses": {
@@ -2258,7 +2258,7 @@
"$ref": "#/components/parameters/preliminary"
},
{
- "$ref": "#/components/parameters/spoiler"
+ "$ref": "#/components/parameters/spoilers"
}
],
"responses": {
@@ -4480,16 +4480,18 @@
"type": "string",
"nullable": true
},
- "duration": {
- "description": "Episode duration in seconds",
- "type": "integer",
- "nullable": true
- },
"aired": {
"description": "Aired Date ISO8601",
"type": "string",
"nullable": true
},
+ "score": {
+ "description": "Aggregated episode score (1.00 - 5.00) based on MyAnimeList user voting",
+ "type": "float",
+ "maximum": "5",
+ "minimum": "1",
+ "nullable": true
+ },
"filler": {
"description": "Filler episode",
"type": "boolean"
@@ -9086,8 +9088,8 @@
"type": "boolean"
}
},
- "spoiler": {
- "name": "spoiler",
+ "spoilers": {
+ "name": "spoilers",
"in": "query",
"description": "Any reviews that are tagged as a spoiler. Spoiler reviews are not returned by default. e.g usage: `?spoiler=true`",
"required": false,
diff --git a/tests/Integration/SeasonControllerTest.php b/tests/Integration/SeasonControllerTest.php
index 6d6a752..ffcde26 100644
--- a/tests/Integration/SeasonControllerTest.php
+++ b/tests/Integration/SeasonControllerTest.php
@@ -190,4 +190,39 @@ class SeasonControllerTest extends TestCase
$this->assertIsArray($content["data"]);
$this->assertCount(2, $content["data"]);
}
+
+ public function testShouldNotIncludeNewlyStartedSeasonOfAnimeInPreviousSeasons()
+ {
+ Carbon::setTestNow(Carbon::parse("2024-10-26"));
+ $f = Anime::factory(1);
+ $startDate = "2024-10-02";
+ $carbonStartDate = Carbon::parse($startDate);
+ $state = $f->serializeStateDefinition([
+ "aired" => new CarbonDateRange($carbonStartDate, null)
+ ]);
+ $state["aired"]["string"] = "Oct 2, 2024 to ?";
+ $state["airing"] = true;
+ $state["status"] = "Currently Airing";
+ $state["premiered"] = "Fall 2024";
+ $state["mal_id"] = 54857;
+ $state["title"] = "Re:Zero kara Hajimeru Isekai Seikatsu 3rd Season";
+ $state["episodes"] = 16;
+ $state["type"] = "TV";
+ $state["duration"] = "23 min per ep";
+ $state["score"] = 8.9;
+ $f->create($state);
+
+ $content = $this->getJsonResponse([], "/v4/seasons/now?filter=tv&continuing&page=1");
+ $this->seeStatusCode(200);
+ $this->assertCount(1, $content["data"]);
+
+ $content = $this->getJsonResponse([], "/v4/seasons/2024/summer?filter=tv&continuing&page=1");
+ $this->seeStatusCode(200);
+ $this->assertCount(0, $content["data"]);
+
+ $content = $this->getJsonResponse([], "/v4/seasons/2024/spring?filter=tv&continuing&page=1");
+ $this->seeStatusCode(200);
+ $this->assertCount(0, $content["data"]);
+ Carbon::setTestNow();
+ }
}
diff --git a/tests/Integration/TopControllerTest.php b/tests/Integration/TopControllerTest.php
index e85ca9f..22f0e46 100644
--- a/tests/Integration/TopControllerTest.php
+++ b/tests/Integration/TopControllerTest.php
@@ -7,6 +7,11 @@ use App\Person;
use App\Testing\ScoutFlush;
use App\Testing\SyntheticMongoDbTransaction;
use Illuminate\Database\Eloquent\Factories\Sequence;
+use Jikan\Exception\BadResponseException;
+use Jikan\Exception\ParserException;
+use Jikan\Model\Reviews\Reviews;
+use Jikan\MyAnimeList\MalClient;
+use Jikan\Parser\Reviews\ReviewsParser;
use Tests\TestCase;
class TopControllerTest extends TestCase
@@ -14,6 +19,15 @@ class TopControllerTest extends TestCase
use SyntheticMongoDbTransaction;
use ScoutFlush;
+ public function topReviewTypeParametersProvider(): array
+ {
+ return [
+ "empty query string" => [[]],
+ "query string = `?type=anime`" => [["type" => "anime"]],
+ "query string = `?type=manga`" => [["type" => "manga"]],
+ ];
+ }
+
public function testTopAnime()
{
Anime::factory(3)->state(new Sequence(
@@ -290,4 +304,27 @@ class TopControllerTest extends TestCase
$this->get('/v4/top/anime/999')
->seeStatusCode(404);
}
+
+ /**
+ * @dataProvider topReviewTypeParametersProvider
+ * @param $params
+ * @return void
+ * @throws BadResponseException
+ * @throws ParserException
+ */
+ public function testTopReviews($params)
+ {
+ $jikanParser = \Mockery::mock(MalClient::class)->makePartial();
+
+ $reviewsParser = \Mockery::mock(ReviewsParser::class)->makePartial();
+ $reviewsParser->allows()->getReviews()->andReturn([]);
+ $reviewsParser->allows()->hasNextPage()->andReturn(false);
+ $reviewsFacade = Reviews::fromParser($reviewsParser);
+
+ /** @noinspection PhpParamsInspection */
+ $jikanParser->allows()->getReviews(\Mockery::any())->andReturn($reviewsFacade);
+ $this->app->instance('JikanParser', $jikanParser);
+ $this->getJsonResponse($params,"/v4/top/reviews");
+ $this->seeStatusCode(200);
+ }
}