From 079ed17e9f476bf169e92b9994f0daea89dfdce2 Mon Sep 17 00:00:00 2001 From: Irfan Date: Sun, 5 Jul 2020 11:29:11 +0500 Subject: [PATCH] Anime V4 endpoints complete --- app/Anime.php | 21 +- app/Episode.php | 47 ++ app/Http/Controllers/V4DB/AnimeController.php | 792 +++++++++++++++++- app/Http/Controllers/V4DB/Controller.php | 118 ++- .../Controllers/V4DB/PersonController.php | 9 +- app/Http/HttpHelper.php | 10 +- app/Http/Middleware/SourceDataManager.php | 105 +++ .../Resources/V4/AnimeCharactersResource.php | 19 + .../Resources/V4/AnimeEpisodeResource.php | 30 + .../Resources/V4/AnimeEpisodesResource.php | 23 + app/Http/Resources/V4/AnimeForumResource.php | 19 + .../Resources/V4/AnimeMoreInfoResource.php | 21 + app/Http/Resources/V4/AnimeNewsResource.php | 23 + .../Resources/V4/AnimePicturesResource.php | 19 + .../V4/AnimeRecommendationsResource.php | 19 + .../Resources/V4/AnimeReviewsResource.php | 19 + app/Http/Resources/V4/AnimeStaffResource.php | 19 + .../Resources/V4/AnimeStatisticsResource.php | 27 + .../Resources/V4/AnimeUserUpdatesResource.php | 19 + app/Http/Resources/V4/AnimeVideosResource.php | 22 + app/Http/Resources/V4/CommonResource.php | 10 + app/Jobs/UpdateCacheJob.php | 5 +- bootstrap/app.php | 16 +- config/controller.php | 137 +++ routes/web.v4.php | 8 +- 25 files changed, 1485 insertions(+), 72 deletions(-) create mode 100644 app/Episode.php create mode 100644 app/Http/Middleware/SourceDataManager.php create mode 100644 app/Http/Resources/V4/AnimeCharactersResource.php create mode 100644 app/Http/Resources/V4/AnimeEpisodeResource.php create mode 100644 app/Http/Resources/V4/AnimeEpisodesResource.php create mode 100644 app/Http/Resources/V4/AnimeForumResource.php create mode 100644 app/Http/Resources/V4/AnimeMoreInfoResource.php create mode 100644 app/Http/Resources/V4/AnimeNewsResource.php create mode 100644 app/Http/Resources/V4/AnimePicturesResource.php create mode 100644 app/Http/Resources/V4/AnimeRecommendationsResource.php create mode 100644 app/Http/Resources/V4/AnimeReviewsResource.php create mode 100644 app/Http/Resources/V4/AnimeStaffResource.php create mode 100644 app/Http/Resources/V4/AnimeStatisticsResource.php create mode 100644 app/Http/Resources/V4/AnimeUserUpdatesResource.php create mode 100644 app/Http/Resources/V4/AnimeVideosResource.php create mode 100644 app/Http/Resources/V4/CommonResource.php create mode 100644 config/controller.php diff --git a/app/Anime.php b/app/Anime.php index b9922e2..23beec0 100644 --- a/app/Anime.php +++ b/app/Anime.php @@ -2,11 +2,13 @@ namespace App; +use App\Http\HttpHelper; use Jenssegers\Mongodb\Eloquent\Model; use Jikan\Helper\Media; use Jikan\Helper\Parser; use Jikan\Jikan; use Jikan\Model\Common\YoutubeMeta; +use Jikan\Request\Anime\AnimeRequest; class Anime extends Model { @@ -53,7 +55,7 @@ class Anime extends Model * @var array */ protected $hidden = [ - '_id', 'expiresAt', 'request_hash', 'trailer_url', 'premiered', 'opening_themes', 'ending_themes', 'images' + '_id', 'trailer_url', 'premiered', 'opening_themes', 'ending_themes', 'images' ]; public function setRelatedAttribute($value) @@ -61,6 +63,8 @@ class Anime extends Model $this->attributes['related'] = $this->getRelatedAttribute(); } +/* + // For V3 or below public function getRelatedAttribute() { // Fix JSON response for empty related object @@ -85,7 +89,7 @@ class Anime extends Model public function setTrailerAttribute($value) { $this->attributes['trailer'] = $this->getTrailerAttribute(); - } + }*/ public function getTrailerAttribute() { @@ -205,4 +209,17 @@ class Anime extends Model ] ]; } + + public static function scrape(int $id) + { + $data = app('JikanParser')->getAnime(new AnimeRequest($id)); + + return HttpHelper::serializeEmptyObjectsControllerLevel( + json_decode( + app('SerializerV4') + ->serialize($data, 'json'), + true + ) + ); + } } \ No newline at end of file diff --git a/app/Episode.php b/app/Episode.php new file mode 100644 index 0000000..602d6d3 --- /dev/null +++ b/app/Episode.php @@ -0,0 +1,47 @@ +where('mal_id', $id) ->get(); - if (empty($results->all())) { + if ($results->isEmpty()) { + $response = Anime::scrape($id); + if (HttpHelper::hasError($response)) { + return HttpResponse::notFound($request); + } + + $response = [ + 'createdAt' => new UTCDateTime(), + 'modifiedAt' => new UTCDateTime() + ] + $response; + + Anime::query() + ->insert($response); + + $results = Anime::query() + ->where('mal_id', $id) + ->get(); + } + + if ($this->isExpired($request, $results)) { + $response = Anime::scrape($id); + if (HttpHelper::hasError($response)) { + return HttpResponse::notFound($request); + } + + $response = [ + 'modifiedAt' => new UTCDateTime() + ] + $response; + + Anime::query() + ->where('request_hash', $this->fingerprint) + ->update($response); + + // requery + $results = Anime::query() + ->where('mal_id', $id) + ->get(); + } + + if ($results->isEmpty()) { return HttpResponse::notFound($request); } - return new \App\Http\Resources\V4\AnimeResource( + $response = (new \App\Http\Resources\V4\AnimeResource( $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request ); } - public function characters_staff(int $id) + public function characters(Request $request, int $id) { - $anime = $this->jikan->getAnimeCharactersAndStaff(new AnimeCharactersAndStaffRequest($id)); - return response($this->serializer->serialize($anime, 'json')); + $results = DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $page = $request->get('page') ?? 1; + $anime = $this->jikan->getAnimeCharactersAndStaff(new AnimeCharactersAndStaffRequest($id)); + $response = \json_decode($this->serializer->serialize($anime, 'json'), true); + + if (HttpHelper::hasError($response)) { + return HttpResponse::notFound($request); + } + + if ($results->isEmpty()) { + $meta = [ + 'createdAt' => new UTCDateTime(), + 'modifiedAt' => new UTCDateTime(), + 'request_hash' => $this->fingerprint + ]; + } + $meta['modifiedAt'] = new UTCDateTime(); + + $response = $meta + $response; + + if ($results->isEmpty()) { + DB::table($this->getRouteTable($request)) + ->insert($response); + } + + if ($this->isExpired($request, $results)) { + DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->update($response); + } + + $results = DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->get(); + } + + $response = (new AnimeCharactersResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); } - public function episode(int $id, int $episodeId) + public function staff(Request $request, int $id) { - $anime = $this->jikan->getAnimeEpisode(new AnimeEpisodeRequest($id, $episodeId)); - return response($this->serializer->serialize($anime, 'json')); + $results = DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $page = $request->get('page') ?? 1; + $anime = $this->jikan->getAnimeCharactersAndStaff(new AnimeCharactersAndStaffRequest($id)); + $response = \json_decode($this->serializer->serialize($anime, 'json'), true); + + if (HttpHelper::hasError($response)) { + return HttpResponse::notFound($request); + } + + if ($results->isEmpty()) { + $meta = [ + 'createdAt' => new UTCDateTime(), + 'modifiedAt' => new UTCDateTime(), + 'request_hash' => $this->fingerprint + ]; + } + $meta['modifiedAt'] = new UTCDateTime(); + + $response = $meta + $response; + + if ($results->isEmpty()) { + DB::table($this->getRouteTable($request)) + ->insert($response); + } + + if ($this->isExpired($request, $results)) { + DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->update($response); + } + + $results = DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->get(); + } + + $response = (new AnimeStaffResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); } - public function episodes(int $id) + public function episode(Request $request, int $id, int $episodeId) { - $page = $_GET['page'] ?? 1; - $anime = $this->jikan->getAnimeEpisodes(new AnimeEpisodesRequest($id, $page)); - return response($this->serializer->serialize($anime, 'json')); + $results = DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $page = $request->get('page') ?? 1; + $anime = $this->jikan->getAnimeEpisode(new AnimeEpisodeRequest($id, $episodeId)); + $response = \json_decode($this->serializer->serialize($anime, 'json'), true); + + if (HttpHelper::hasError($response)) { + return HttpResponse::notFound($request); + } + + if ($results->isEmpty()) { + $meta = [ + 'createdAt' => new UTCDateTime(), + 'modifiedAt' => new UTCDateTime(), + 'request_hash' => $this->fingerprint + ]; + } + $meta['modifiedAt'] = new UTCDateTime(); + + $response = $meta + $response; + + if ($results->isEmpty()) { + DB::table($this->getRouteTable($request)) + ->insert($response); + } + + if ($this->isExpired($request, $results)) { + DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->update($response); + } + + $results = DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->get(); + } + + $response = (new AnimeEpisodeResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); } - public function news(int $id) + public function episodes(Request $request, int $id) { - $anime = ['articles' => $this->jikan->getNewsList(new AnimeNewsRequest($id))]; - return response($this->serializer->serialize($anime, 'json')); + $results = DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $page = $request->get('page') ?? 1; + $anime = $this->jikan->getAnimeEpisodes(new AnimeEpisodesRequest($id, $page)); + $response = \json_decode($this->serializer->serialize($anime, 'json'), true); + + if (HttpHelper::hasError($response)) { + return HttpResponse::notFound($request); + } + + if ($results->isEmpty()) { + $meta = [ + 'createdAt' => new UTCDateTime(), + 'modifiedAt' => new UTCDateTime(), + 'request_hash' => $this->fingerprint + ]; + } + $meta['modifiedAt'] = new UTCDateTime(); + + $response = $meta + $response; + + if ($results->isEmpty()) { + DB::table($this->getRouteTable($request)) + ->insert($response); + } + + if ($this->isExpired($request, $results)) { + DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->update($response); + } + + $results = DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->get(); + } + + $response = (new AnimeEpisodesResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); } - public function forum(int $id) + public function news(Request $request, int $id) { - $anime = ['topics' => $this->jikan->getAnimeForum(new AnimeForumRequest($id))]; - return response($this->serializer->serialize($anime, 'json')); + $results = DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $page = $request->get('page') ?? 1; // todo add page support to parser + $anime = ['articles' => $this->jikan->getNewsList(new AnimeNewsRequest($id))]; + $response = \json_decode($this->serializer->serialize($anime, 'json'), true); + + if (HttpHelper::hasError($response)) { + return HttpResponse::notFound($request); + } + + if ($results->isEmpty()) { + $meta = [ + 'createdAt' => new UTCDateTime(), + 'modifiedAt' => new UTCDateTime(), + 'request_hash' => $this->fingerprint + ]; + } + $meta['modifiedAt'] = new UTCDateTime(); + + $response = $meta + $response; + + if ($results->isEmpty()) { + DB::table($this->getRouteTable($request)) + ->insert($response); + } + + if ($this->isExpired($request, $results)) { + DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->update($response); + } + + $results = DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->get(); + } + + $response = (new AnimeNewsResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); } - public function videos(int $id) + public function forum(Request $request, int $id) { - $anime = $this->jikan->getAnimeVideos(new AnimeVideosRequest($id)); - return response($this->serializer->serialize($anime, 'json')); + $results = DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $topic = $request->get('topic'); + $anime = ['topics' => $this->jikan->getAnimeForum(new AnimeForumRequest($id, $topic))]; + $response = \json_decode($this->serializer->serialize($anime, 'json'), true); + + if (HttpHelper::hasError($response)) { + return HttpResponse::notFound($request); + } + + if ($results->isEmpty()) { + $meta = [ + 'createdAt' => new UTCDateTime(), + 'modifiedAt' => new UTCDateTime(), + 'request_hash' => $this->fingerprint + ]; + } + $meta['modifiedAt'] = new UTCDateTime(); + + $response = $meta + $response; + + if ($results->isEmpty()) { + DB::table($this->getRouteTable($request)) + ->insert($response); + } + + if ($this->isExpired($request, $results)) { + DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->update($response); + } + + $results = DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->get(); + } + + $response = (new AnimeForumResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); } - public function pictures(int $id) + public function videos(Request $request, int $id) { - $anime = ['pictures' => $this->jikan->getAnimePictures(new AnimePicturesRequest($id))]; - return response($this->serializer->serialize($anime, 'json')); + $results = DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $anime = $this->jikan->getAnimeVideos(new AnimeVideosRequest($id)); + $response = \json_decode($this->serializer->serialize($anime, 'json'), true); + + if (HttpHelper::hasError($response)) { + return HttpResponse::notFound($request); + } + + if ($results->isEmpty()) { + $meta = [ + 'createdAt' => new UTCDateTime(), + 'modifiedAt' => new UTCDateTime(), + 'request_hash' => $this->fingerprint + ]; + } + $meta['modifiedAt'] = new UTCDateTime(); + + $response = $meta + $response; + + if ($results->isEmpty()) { + DB::table($this->getRouteTable($request)) + ->insert($response); + } + + if ($this->isExpired($request, $results)) { + DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->update($response); + } + + $results = DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->get(); + } + + $response = (new AnimeVideosResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); } - public function stats(int $id) + public function pictures(Request $request, int $id) { - $anime = $this->jikan->getAnimeStats(new AnimeStatsRequest($id)); - return response($this->serializer->serialize($anime, 'json')); + $results = DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $anime = ['pictures' => $this->jikan->getAnimePictures(new AnimePicturesRequest($id))]; + $response = \json_decode($this->serializer->serialize($anime, 'json'), true); + + if (HttpHelper::hasError($response)) { + return HttpResponse::notFound($request); + } + + if ($results->isEmpty()) { + $meta = [ + 'createdAt' => new UTCDateTime(), + 'modifiedAt' => new UTCDateTime(), + 'request_hash' => $this->fingerprint + ]; + } + $meta['modifiedAt'] = new UTCDateTime(); + + $response = $meta + $response; + + if ($results->isEmpty()) { + DB::table($this->getRouteTable($request)) + ->insert($response); + } + + if ($this->isExpired($request, $results)) { + DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->update($response); + } + + $results = DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->get(); + } + + $response = (new AnimePicturesResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); } - public function moreInfo(int $id) + public function stats(Request $request, int $id) { - $anime = ['moreinfo' => $this->jikan->getAnimeMoreInfo(new AnimeMoreInfoRequest($id))]; - return response(json_encode($anime)); + $results = DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $anime = $this->jikan->getAnimeStats(new AnimeStatsRequest($id)); + $response = \json_decode($this->serializer->serialize($anime, 'json'), true); + + if (HttpHelper::hasError($response)) { + return HttpResponse::notFound($request); + } + + if ($results->isEmpty()) { + $meta = [ + 'createdAt' => new UTCDateTime(), + 'modifiedAt' => new UTCDateTime(), + 'request_hash' => $this->fingerprint + ]; + } + $meta['modifiedAt'] = new UTCDateTime(); + + $response = $meta + $response; + + if ($results->isEmpty()) { + DB::table($this->getRouteTable($request)) + ->insert($response); + } + + if ($this->isExpired($request, $results)) { + DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->update($response); + } + + $results = DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->get(); + } + + $response = (new AnimeStatisticsResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); } - public function recommendations(int $id) + public function moreInfo(Request $request, int $id) { - $anime = ['recommendations' => $this->jikan->getAnimeRecommendations(new AnimeRecommendationsRequest($id))]; - return response($this->serializer->serialize($anime, 'json')); + $results = DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $anime = ['moreinfo' => $this->jikan->getAnimeMoreInfo(new AnimeMoreInfoRequest($id))]; + $response = \json_decode($this->serializer->serialize($anime, 'json'), true); + + if (HttpHelper::hasError($response)) { + return HttpResponse::notFound($request); + } + + if ($results->isEmpty()) { + $meta = [ + 'createdAt' => new UTCDateTime(), + 'modifiedAt' => new UTCDateTime(), + 'request_hash' => $this->fingerprint + ]; + } + $meta['modifiedAt'] = new UTCDateTime(); + + $response = $meta + $response; + + if ($results->isEmpty()) { + DB::table($this->getRouteTable($request)) + ->insert($response); + } + + if ($this->isExpired($request, $results)) { + DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->update($response); + } + + $results = DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->get(); + } + + $response = (new AnimeMoreInfoResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); } - public function userupdates(int $id, int $page = 1) + public function recommendations(Request $request, int $id) { - $anime = ['users' => $this->jikan->getAnimeRecentlyUpdatedByUsers(new AnimeRecentlyUpdatedByUsersRequest($id, $page))]; - return response($this->serializer->serialize($anime, 'json')); + $results = DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $anime = ['recommendations' => $this->jikan->getAnimeRecommendations(new AnimeRecommendationsRequest($id))]; + $response = \json_decode($this->serializer->serialize($anime, 'json'), true); + + if (HttpHelper::hasError($response)) { + return HttpResponse::notFound($request); + } + + if ($results->isEmpty()) { + $meta = [ + 'createdAt' => new UTCDateTime(), + 'modifiedAt' => new UTCDateTime(), + 'request_hash' => $this->fingerprint + ]; + } + $meta['modifiedAt'] = new UTCDateTime(); + + $response = $meta + $response; + + if ($results->isEmpty()) { + DB::table($this->getRouteTable($request)) + ->insert($response); + } + + if ($this->isExpired($request, $results)) { + DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->update($response); + } + + $results = DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->get(); + } + + $response = (new AnimeRecommendationsResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); } - public function reviews(int $id, int $page = 1) + public function userupdates(Request $request, int $id) { - $anime = ['reviews' => $this->jikan->getAnimeReviews(new AnimeReviewsRequest($id, $page))]; - return response($this->serializer->serialize($anime, 'json')); + $results = DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $page = $request->get('page') ?? 1; + $anime = ['users' => $this->jikan->getAnimeRecentlyUpdatedByUsers(new AnimeRecentlyUpdatedByUsersRequest($id, $page))]; + $response = \json_decode($this->serializer->serialize($anime, 'json'), true); + + if (HttpHelper::hasError($response)) { + return HttpResponse::notFound($request); + } + + if ($results->isEmpty()) { + $meta = [ + 'createdAt' => new UTCDateTime(), + 'modifiedAt' => new UTCDateTime(), + 'request_hash' => $this->fingerprint + ]; + } + $meta['modifiedAt'] = new UTCDateTime(); + + $response = $meta + $response; + + if ($results->isEmpty()) { + DB::table($this->getRouteTable($request)) + ->insert($response); + } + + if ($this->isExpired($request, $results)) { + DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->update($response); + } + + $results = DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->get(); + } + + $response = (new AnimeUserUpdatesResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + public function reviews(Request $request, int $id) + { + $results = DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $page = $request->get('page') ?? 1; + $anime = ['reviews' => $this->jikan->getAnimeReviews(new AnimeReviewsRequest($id, $page))]; + $response = \json_decode($this->serializer->serialize($anime, 'json'), true); + + if (HttpHelper::hasError($response)) { + return HttpResponse::notFound($request); + } + + if ($results->isEmpty()) { + $meta = [ + 'createdAt' => new UTCDateTime(), + 'modifiedAt' => new UTCDateTime(), + 'request_hash' => $this->fingerprint + ]; + } + $meta['modifiedAt'] = new UTCDateTime(); + + $response = $meta + $response; + + if ($results->isEmpty()) { + DB::table($this->getRouteTable($request)) + ->insert($response); + } + + if ($this->isExpired($request, $results)) { + DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->update($response); + } + + $results = DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->get(); + } + + $response = (new AnimeReviewsResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); } } diff --git a/app/Http/Controllers/V4DB/Controller.php b/app/Http/Controllers/V4DB/Controller.php index bab9923..f26bdb0 100644 --- a/app/Http/Controllers/V4DB/Controller.php +++ b/app/Http/Controllers/V4DB/Controller.php @@ -6,12 +6,16 @@ use App\Http\HttpHelper; use App\Providers\SerializerFactory; use App\Providers\SerializerServiceProdivder; use App\Providers\SerializerServiceProviderV3; +use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Jikan\Jikan; use Jikan\MyAnimeList\MalClient; use JMS\Serializer\Context; use JMS\Serializer\Serializer; use Laravel\Lumen\Routing\Controller as BaseController; +use mysql_xdevapi\Exception; +use r\Queries\Control\Http; +use Symfony\Component\HttpFoundation\Response; /** * Class Controller @@ -39,34 +43,121 @@ class Controller extends BaseController */ private $response; + protected $expired = false; + + protected $fingerprint; + /** * AnimeController constructor. * * @param Serializer $serializer * @param MalClient $jikan */ - public function __construct(MalClient $jikan) + public function __construct(Request $request, MalClient $jikan) { $this->serializer = SerializerFactory::createV4(); $this->jikan = $jikan; + $this->fingerprint = HttpHelper::resolveRequestFingerprint($request); + } + + protected function isExpired($request, $results) : bool + { + try { + if ($results->first()->modifiedAt === null) { + return true; + } + + $modifiedAt = (int) $results->first()->modifiedAt->toDateTime()->format('U'); + $routeName = HttpHelper::getRouteName($request); + $expiry = (int) config("controller.{$routeName}.ttl") + $modifiedAt; + + if (time() > $expiry) { + return true; + } + } catch (\Exception $e) { + return false; + } + + return false; + } + + protected function getExpiry($results, $request) + { + $modifiedAt = $this->getLastModified($results); + + $routeName = HttpHelper::getRouteName($request); + return (int) config("controller.{$routeName}.ttl") + $modifiedAt; + } + + protected function getTtl($results, $request) + { + $routeName = HttpHelper::getRouteName($request); + return (int) config("controller.{$routeName}.ttl"); + } + + protected function getLastModified($results) : int + { + if (is_array($results->first())) { + return (int) $results->first()['modifiedAt']->toDateTime()->format('U'); + } + + if (is_object($results->first())) { + return (int) $results->first()->modifiedAt->toDateTime()->format('U'); + } + + throw new \Exception('Failed to get Last Modified'); + } + + protected function serialize($data) : array + { + return \json_decode( + $this->serializer->serialize($data, 'json') + ); + } + + protected function getRouteName($request) : string + { + return HttpHelper::getRouteName($request); + } + + protected function getRouteTable($request) : string + { + return config("controller.{$this->getRouteName($request)}.table_name"); + } + + protected function prepareResponse($response, $results, $request) + { + return $response + ->header('X-Request-Fingerprint', $this->fingerprint) + ->setTtl($this->getTtl($results, $request)) + ->setExpires( + (new \DateTimeImmutable())->setTimestamp( + $this->getExpiry($results, $request) + ) + ) + ->setLastModified( + (new \DateTimeImmutable())->setTimestamp( + $this->getLastModified($results) + ) + ); } /** * @param array $response * @return array */ - protected function prepareResponse(Request $request, array $response) : array - { - $this->request = $request; - $this->response = $response; - - unset($this->response['_id']); - - $this->mutation(); - - $this->response = ['data' => $this->response]; - return $this->response; - } +// protected function prepareResponse(Request $request, array $response) : array +// { +// $this->request = $request; +// $this->response = $response; +// +// unset($this->response['_id']); +// +// $this->mutation(); +// +// $this->response = ['data' => $this->response]; +// return $this->response; +// } /** * @param Request $request @@ -96,4 +187,5 @@ class Controller extends BaseController } } } + } diff --git a/app/Http/Controllers/V4DB/PersonController.php b/app/Http/Controllers/V4DB/PersonController.php index f2f1601..3f9a8b0 100644 --- a/app/Http/Controllers/V4DB/PersonController.php +++ b/app/Http/Controllers/V4DB/PersonController.php @@ -2,15 +2,18 @@ namespace App\Http\Controllers\V4DB; +use Illuminate\Http\Request; use Jikan\Request\Person\PersonRequest; use Jikan\Request\Person\PersonPicturesRequest; class PersonController extends Controller { - public function main(int $id) + public function main(Request $request, int $id) { - $person = $this->jikan->getPerson(new PersonRequest($id)); - return response($this->serializer->serialize($person, 'json')); + if ($request->header('auth') === env('APP_KEY')) { + $person = $this->jikan->getPerson(new PersonRequest($id)); + return response($this->serializer->serialize($person, 'json')); + } } public function pictures(int $id) diff --git a/app/Http/HttpHelper.php b/app/Http/HttpHelper.php index ecc4865..79b0168 100755 --- a/app/Http/HttpHelper.php +++ b/app/Http/HttpHelper.php @@ -83,12 +83,11 @@ class HttpHelper return $data; } - public static function requestControllerName(Request $request) : string + public static function getRouteName(Request $request) : string { $route = explode('\\', $request->route()[1]['uses']); - $route = end($route); - return explode('@', $route)[0]; + return end($route); } public static function getRequestUriHash(Request $request) : string @@ -96,4 +95,9 @@ class HttpHelper return sha1($request->getRequestUri()); } + public static function getRouteTable($request) : string + { + $routeName = HttpHelper::getRouteName($request); + return config("controller.{$routeName}.table_name"); + } } diff --git a/app/Http/Middleware/SourceDataManager.php b/app/Http/Middleware/SourceDataManager.php new file mode 100644 index 0000000..4381585 --- /dev/null +++ b/app/Http/Middleware/SourceDataManager.php @@ -0,0 +1,105 @@ +header('auth') === env('APP_KEY') + || empty($request->segments()) + || !isset($request->segments()[1]) + || \in_array('meta', $request->segments()) + ) { + return $next($request); + } + + // todo check if mongo enabled + + $routeExpanded = explode( + '\\', + $request->route()[1]['uses'] + ); + $route = end($routeExpanded); + + +// if (!\in_array($route, self::STORABLE_DATA)) { +// return $next($request); +// } + $fingerprint = HttpHelper::resolveRequestFingerprint($request); + $table = DatabaseHandler::getMappedTableName($route); + $document = DB::table($table)->where('request_hash', $fingerprint); + $documentExists = $document->exists(); + + // Data exists + if ($documentExists) { + // check expiry and dispatch job request if needed + $expiry = $document->get('expiresAt'); + dd('document exists!'); + return; + } + + // If cache does not exist + if (!$documentExists) { + $response = $next($request); + + $freshRequest = Request::create( + env('APP_URL') . $request->getRequestUri(), 'GET', [ + 'auth' => env('APP_KEY') + ] + ); + $freshResponse = app()->handle($freshRequest); + + dd($freshResponse); + + // skip DB, start scraping +// $response = app('GuzzleClient') +// ->request( +// 'GET', +// env('APP_URL') . $request->getRequestUri(), +// [ +// 'headers' => [ +// 'auth' => env('APP_KEY') +// ] +// ] +// ); +// +// dd($response); + + if (HttpHelper::hasError($response)) { + return $response; + } + +// $results = DB::table($table)->insert( +// [ +// 'created_at' => new UTCDateTime(), +// 'modified_at' => new UTCDateTime(), +// 'request_hash' => $fingerprint +// ] + $response +// ); +// +// dd($results); + } + } + +} diff --git a/app/Http/Resources/V4/AnimeCharactersResource.php b/app/Http/Resources/V4/AnimeCharactersResource.php new file mode 100644 index 0000000..9cc34c2 --- /dev/null +++ b/app/Http/Resources/V4/AnimeCharactersResource.php @@ -0,0 +1,19 @@ + $this['mal_id'], + 'url' => $this['url'], + 'title' => $this['title'], + 'title_japanese' => $this['title_japanese'], + 'title_romanji' => $this['title_romanji'], + 'duration' => $this['duration'], + 'aired' => $this['aired'], + 'filler' => $this['filler'], + 'recap' => $this['recap'], + 'synopsis' => $this['synopsis'] + ]; + } +} \ No newline at end of file diff --git a/app/Http/Resources/V4/AnimeEpisodesResource.php b/app/Http/Resources/V4/AnimeEpisodesResource.php new file mode 100644 index 0000000..0694124 --- /dev/null +++ b/app/Http/Resources/V4/AnimeEpisodesResource.php @@ -0,0 +1,23 @@ + $this['episodes_last_page'], + 'has_next_page' => $this['has_next_page'] ?? false, + 'episodes' => $this['episodes'] + ]; + } +} \ No newline at end of file diff --git a/app/Http/Resources/V4/AnimeForumResource.php b/app/Http/Resources/V4/AnimeForumResource.php new file mode 100644 index 0000000..57bca6b --- /dev/null +++ b/app/Http/Resources/V4/AnimeForumResource.php @@ -0,0 +1,19 @@ + $this['moreinfo'] + ]; + } +} \ No newline at end of file diff --git a/app/Http/Resources/V4/AnimeNewsResource.php b/app/Http/Resources/V4/AnimeNewsResource.php new file mode 100644 index 0000000..5f64ffd --- /dev/null +++ b/app/Http/Resources/V4/AnimeNewsResource.php @@ -0,0 +1,23 @@ + $this['last_visible_page'] ?? null, + 'has_next_page' => $this['has_next_page'] ?? false, + 'articles' => $this['articles'] + ]; + } +} \ No newline at end of file diff --git a/app/Http/Resources/V4/AnimePicturesResource.php b/app/Http/Resources/V4/AnimePicturesResource.php new file mode 100644 index 0000000..660f90c --- /dev/null +++ b/app/Http/Resources/V4/AnimePicturesResource.php @@ -0,0 +1,19 @@ + $this['watching'], + 'completed' => $this['completed'], + 'on_hold' => $this['on_hold'], + 'dropped' => $this['dropped'], + 'plan_to_watch' => $this['plan_to_watch'], + 'total' => $this['total'], + 'scores' => $this['scores'] + ]; + } +} \ No newline at end of file diff --git a/app/Http/Resources/V4/AnimeUserUpdatesResource.php b/app/Http/Resources/V4/AnimeUserUpdatesResource.php new file mode 100644 index 0000000..487c491 --- /dev/null +++ b/app/Http/Resources/V4/AnimeUserUpdatesResource.php @@ -0,0 +1,19 @@ + $this['promo'], + 'episodes' => $this['episodes'] + ]; + } +} \ No newline at end of file diff --git a/app/Http/Resources/V4/CommonResource.php b/app/Http/Resources/V4/CommonResource.php new file mode 100644 index 0000000..e71746c --- /dev/null +++ b/app/Http/Resources/V4/CommonResource.php @@ -0,0 +1,10 @@ +requestCached = (bool) Cache::has($this->fingerprint); } - /** * @throws \GuzzleHttp\Exception\GuzzleException */ @@ -57,9 +56,7 @@ class UpdateCacheJob extends Job $queueFingerprint = "queue_update:{$this->fingerprint}"; - $client = new Client(); - - $response = $client + $response = app('GuzzleClient') ->request( 'GET', env('APP_URL') . $this->requestUri, diff --git a/bootstrap/app.php b/bootstrap/app.php index 26e10aa..9f0af3c 100755 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -1,5 +1,6 @@ routeMiddleware([ // 'throttle' => App\Http\Middleware\Throttle::class, // 'etag' => \App\Http\Middleware\EtagMiddleware::class, // 'microcaching' => \App\Http\Middleware\MicroCaching::class, - 'database-resolver' => \App\Http\Middleware\DatabaseResolver::class, +// 'database-resolver' => \App\Http\Middleware\DatabaseResolver::class, + 'source-data-manager' => \App\Http\Middleware\SourceDataManager::class, // 'source-health-monitor' => \App\Http\Middleware\SourceHealthMonitor::class ]); @@ -95,6 +97,7 @@ $app->routeMiddleware([ $app->configure('database'); $app->configure('queue'); $app->configure('controller-to-table-mapping'); +$app->configure('controller'); if (env('CACHING')) { $app->configure('cache'); @@ -109,6 +112,8 @@ $app->instance('GuzzleClient', $guzzleClient); $jikan = new \Jikan\MyAnimeList\MalClient(app('GuzzleClient')); $app->instance('JikanParser', $jikan); +$app->instance('SerializerV4', SerializerFactory::createV4()); + if (env('SOURCE_BAD_HEALTH_FAILOVER') && env('DB_CACHING')) { $app->register(\App\Providers\SourceHealthServiceProvider::class); } @@ -135,11 +140,12 @@ $commonMiddleware = [ // 'slave-auth', // 'meta', // 'etag', - 'database-resolver', +// 'database-resolver', // 'microcaching', // 'cache-resolver', // 'throttle' // 'source-health-monitor' +// 'source-data-manager' ]; $app->router->group( @@ -168,15 +174,13 @@ $app->router->group( $app->router->group( [ 'prefix' => '/', - 'namespace' => 'App\Http\Controllers\V3', - 'middleware' => $commonMiddleware ], function ($router) { $router->get('/', function () { return response()->json([ 'author_url' => 'http://irfan.dahir.co', - 'discord_url' => 'https://discord.gg/4tvCr36', - 'version' => '3.4', + 'discord_url' => 'http://discord.jikan.moe', + 'version' => '4.0', 'parser_version' => JIKAN_PARSER_VERSION, 'website_url' => 'https://jikan.moe', 'documentation_url' => 'https://jikan.docs.apiary.io', diff --git a/config/controller.php b/config/controller.php new file mode 100644 index 0000000..24c9225 --- /dev/null +++ b/config/controller.php @@ -0,0 +1,137 @@ + [ + 'table_name' => 'anime', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + 'AnimeController@characters_staff' => [ + 'table_name' => 'anime_characters_staff', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + 'AnimeController@characters' => [ + 'table_name' => 'anime_characters_staff', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + 'AnimeController@staff' => [ + 'table_name' => 'anime_characters_staff', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + 'AnimeController@episodes' => [ + 'table_name' => 'anime_episodes', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + 'AnimeController@episode' => [ + 'table_name' => 'anime_episode', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + 'AnimeController@news' => [ + 'table_name' => 'anime_news', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + 'AnimeController@forum' => [ + 'table_name' => 'anime_forum', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + 'AnimeController@videos' => [ + 'table_name' => 'anime_videos', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + 'AnimeController@pictures' => [ + 'table_name' => 'anime_pictures', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + 'AnimeController@stats' => [ + 'table_name' => 'anime_stats', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + 'AnimeController@moreInfo' => [ + 'table_name' => 'anime_moreinfo', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + 'AnimeController@recommendations' => [ + 'table_name' => 'anime_recommendations', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + 'AnimeController@userupdates' => [ + 'table_name' => 'anime_userupdates', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + 'AnimeController@reviews' => [ + 'table_name' => 'anime_reviews', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + + 'MangaController@main' => 'manga', + 'MangaController@characters' => 'manga_characters', + 'MangaController@news' => 'manga_news', + 'MangaController@forum' => 'manga_forum', + 'MangaController@pictures' => 'manga_pictures', + 'MangaController@stats' => 'manga_stats', + 'MangaController@moreInfo' => 'manga_moreinfo', + 'MangaController@recommendations' => 'manga_recommendations', + 'MangaController@userupdates' => 'manga_userupdates', + 'MangaController@reviews' => 'manga_reviews', + + 'CharacterController@main' => 'characters', + 'CharacterController@pictures' => 'characters_pictures', + + 'PersonController@main' => 'people', + 'PersonController@pictures' => 'people_pictures', + + 'SeasonController@archive' => 'season_archive', + 'SeasonController@later' => 'season_later', + 'SeasonController@main' => 'season', + + 'ScheduleController@main' => 'schedule', + + 'ProducerController@main' => 'producers', + 'ProducerController@resource' => 'producers_anime', + + 'MagazineController@main' => 'magazines', + 'MagazineController@resource' => 'magazines_manga', + + 'UserController@recentlyOnline' => 'users_recently_online', + 'UserController@profile' => 'users', + 'UserController@history' => 'users_history', + 'UserController@friends' => 'users_friends', + 'UserController@animelist' => 'users_animelist', + 'UserController@mangalist' => 'users_mangalist', + 'UserController@recommendations' => 'users_recommendations', + 'UserController@reviews' => 'users_reviews', + 'UserController@clubs' => 'users_clubs', + + 'GenreController@animeListing' => 'genres', + 'GenreController@mangaListing' => 'genres', + 'GenreController@anime' => 'genres_anime', + 'GenreController@manga' => 'genres_manga', + + 'TopController@anime' => 'top_anime', + 'TopController@manga' => 'top_manga', + 'TopController@characters' => 'top_characters', + 'TopController@people' => 'top_people', + 'ReviewsController@bestVoted' => 'top_reviews', + + 'SearchController@anime' => 'search_anime', + 'SearchController@manga' => 'search_manga', + 'SearchController@character' => 'search_characters', + 'SearchController@people' => 'search_people', + 'SearchController@users' => 'search_users', + 'SearchController@userById' => 'search_users_by_id', + + 'ClubController@main' => 'clubs', + 'ClubController@members' => 'clubs_members', + + 'ReviewsController@anime' => 'reviews', + 'ReviewsController@manga' => 'reviews', + + 'RecommendationsController@anime' => 'recommendations', + 'RecommendationsController@manga' => 'recommendations', + + 'WatchController@recentEpisodes' => 'watch', + 'WatchController@popularEpisodes' => 'watch', + 'WatchController@recentPromos' => 'watch', + 'WatchController@popularPromos' => 'watch', + +]; \ No newline at end of file diff --git a/routes/web.v4.php b/routes/web.v4.php index 42eb0d2..8d561c8 100755 --- a/routes/web.v4.php +++ b/routes/web.v4.php @@ -75,8 +75,12 @@ $router->group( 'uses' => 'AnimeController@forum' ]); - $router->get('/media', [ - 'uses' => 'AnimeController@media' + $router->get('/videos', [ + 'uses' => 'AnimeController@videos' + ]); + + $router->get('/pictures', [ + 'uses' => 'AnimeController@pictures' ]); $router->get('/statistics', [