diff --git a/.env.dist b/.env.dist index 5c23d02..5446600 100755 --- a/.env.dist +++ b/.env.dist @@ -1,33 +1,127 @@ +### +# App +### APP_ENV=production APP_DEBUG=false APP_KEY= APP_TIMEZONE=UTC APP_URL=http://localhost +APP_VERSION="4.0 Beta" -CACHE_DRIVER=file -QUEUE_CONNECTION=redis +### +# Database Caching (MongoDB) +### +DB_CACHING=true +DB_CONNECTION=mongodb +DB_HOST=localhost +DB_PORT=27017 +DB_DATABASE=jikan +DB_ADMIN=jikan +DB_USERNAME= +DB_PASSWORD= -CACHE_METHOD=legacy -CACHE_DEFAULT_EXPIRE=86400 -CACHE_META_EXPIRE=300 -CACHE_USER_EXPIRE=300 -CACHE_404_EXPIRE=604800 -CACHE_SEARCH_EXPIRE=432000 +### +# Database query default values +### +MAX_RESULTS_PER_PAGE=30 +### +# Enable MyAnimeList Heartbeat +# +# Monitor bad requests to determine whether MyAnimeList is down +# +# Fallback once the following threshold is reached +### +SOURCE=local +SOURCE_BAD_HEALTH_THRESHOLD=10 +# Recheck source availability (in seconds) +SOURCE_BAD_HEALTH_RECHECK=10 +# Fail count only within specified time range (in seconds) +SOURCE_BAD_HEALTH_RANGE=30 +# Max Fail stores +SOURCE_BAD_HEALTH_MAX_STORE=50 +# Disable failover if the score reaches the following (0.0-1.0 values ONLY) +# e.g 0.9 means 90% successful requests to MyAnimeList +SOURCE_GOOD_HEALTH_SCORE=0.9 +# Max time request is allowed to take +# https://curl.haxx.se/libcurl/c/CURLOPT_TIMEOUT.html +SOURCE_TIMEOUT=10 + +### +# Caching (File, Redis, etc) +# Can be added over DB Caching +### +CACHING=false +CACHE_DRIVER=array +CACHE_METHOD=queue + +# Caching TTL (in seconds) on specific endpoints +CACHE_DEFAULT_EXPIRE=86400 # 1 day +CACHE_META_EXPIRE=300 # 5 minutes +CACHE_USER_EXPIRE=300 # 5 minutes +CACHE_USERLIST_EXPIRE=3600 # 1 hour +CACHE_404_EXPIRE=604800 # 7 days +CACHE_SEARCH_EXPIRE=432000 # 5 days +CACHE_PRODUCERS_EXPIRE=432000 # 5 days +CACHE_MAGAZINES_EXPIRE=432000 # 5 days + + +### +# Redis Caching Configuration +### +REDIS_HOST=127.0.0.1 +REDIS_PASSWORD=null +REDIS_PORT=6379 + +### +# Micro Caching +# Uses CACHE_DRIVER +### MICROCACHING=false MICROCACHING_EXPIRE=5 +### +# Queue management +# Uses QUEUE_CONNECTION as queue storage (MongoDB, Redis, etc) +### +QUEUE_CONNECTION=database +QUEUE_TABLE=jobs +QUEUE_FAILED_TABLE=jobs_failed QUEUE_DELAY_PER_JOB=5 +### +# Throttling +# Rate limiting requests +### THROTTLE=false THROTTLE_DECAY_MINUTES=1 THROTTLE_MAX_REQUESTS_PER_DECAY_MINUTES=60 THROTTLE_MAX_REQUESTS_PER_SECOND=2 -REDIS_HOST=127.0.0.1 -REDIS_PASSWORD=null -REDIS_PORT=6379 - +### +# GitHub generate report URL on fatal errors +### GITHUB_REPORTING=true GITHUB_REST="jikan-me/jikan-rest" -GITHUB_API="jikan-me/jikan" \ No newline at end of file +GITHUB_API="jikan-me/jikan" + +### +# OpenAPI +### +SWAGGER_VERSION=3.0 + +### +# API call insights +### +# Enable/Disable insights API system +INSIGHTS=false #WIP +# Max requests store in seconds - default 2 days +INSIGHTS_MAX_STORE_TIME=172800 + +### +# Error reporting +### +REPORTING=true +REPORTING_DRIVER=sentry +SENTRY_LARAVEL_DSN="https://examplePublicKey@o0.ingest.sentry.io/0" +SENTRY_TRACES_SAMPLE_RATE=1 \ No newline at end of file diff --git a/.gitignore b/.gitignore index dd1b196..1fe0fb9 100755 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,11 @@ /vendor -/vendor.v4 /.idea Homestead.json Homestead.yaml -.env.v4 +.env composer.phar composer.lock .php_cs.cache -.env +.env.v4 /storage/app/indexer /storage/app/failovers.json \ No newline at end of file diff --git a/COMMANDS.MD b/COMMANDS.MD index b88d229..6387959 100644 --- a/COMMANDS.MD +++ b/COMMANDS.MD @@ -11,7 +11,10 @@ For an entire list of commands, you can run `php artisan list` - [Remove](#cache-remove) - [Change Cache Driver](#cache-change-cache-driver) - [Change Cache Method](#cache-change-cache-method) - +- [Indexer](#indexer) + - [Anime](#anime) + - [Manga](#manga) + ## Commands ### Serve @@ -56,4 +59,53 @@ Command: `cache:method {method}` Example: `cache:method queue` -[Read more on how it works](https://github.com/jikan-me/jikan-rest/blob/master/README.md#06-configuring-how-jikan-handles-expired-cache-optional) \ No newline at end of file +[Read more on how it works](https://github.com/jikan-me/jikan-rest/blob/master/README.md#06-configuring-how-jikan-handles-expired-cache-optional) + + +#### Indexer: Anime +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. + +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. + +⚠ This is strictly for performance and experience and providing better search functionality. Don't build your own anime database as that's against MyAnimeList's Terms of Service. + +Command: +``` +indexer:anime + {--failed : Run only entries that failed to index last time} + {--resume : Resume from the last position} + {--reverse : Start from the end of the array} + {--index=0 : Start from a specific index} + {--delay=3 : Set a delay between requests} +``` + +Example: `indexer:anime --reverse --delay=5 --failed` + +This translates to running entries that previously failed to index or update, in reverse, with a delay of 5 seconds between each request. + +#### Indexer: Manga +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. + +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. + +⚠ This is strictly for performance and experience and providing better search functionality. Don't build your own anime database as that's against MyAnimeList's Terms of Service. + +Command: +``` +indexer:anime + {--failed : Run only entries that failed to index last time} + {--resume : Resume from the last position} + {--reverse : Start from the end of the array} + {--index=0 : Start from a specific index} + {--delay=3 : Set a delay between requests} +``` + +Example: `indexer:manga` + +This simply translates to running the indexer without any additional configuration. \ No newline at end of file diff --git a/MIGRATION.MD b/MIGRATION.MD deleted file mode 100755 index 22d331f..0000000 --- a/MIGRATION.MD +++ /dev/null @@ -1,181 +0,0 @@ -# Jikan REST Migration (v2 -> v3) - -## NOTICE! -- Any key that holds an array value will be an empty array if there's nothing -- Any key that holds anything other than an array (string, int, float) will be `null` if there's nothing - -## Anime -- Added `aired['string']` -- Added `trailer_url` -- Removed `airing_string` -- `link_canonical` -> `url` -- `title_synonyms` is now an array -- `producer` -> `producers` -- `licensor` -> `licensors` -- `studio` -> `studios` -- `genre` -> `genres` -- `opening_theme` -> `opening_themes` -- `ending_theme` -> `ending_themes` -## Anime : /episodes -- `episode` -> `episodes` - - `id` -> `episode_id` - - `aired` is now an array -- `episode_last_page` -> `episodes_last_page` - -## Anime : /characters\_and\_staff -- `character` -> `characters` - `voice_actor` -> `voice_actors` -- `staff` - - `role` -> `positions` - - `positions` is now an array - -## Anime : /news -- `news` -> `articles` -- `date` is now in ISO8601 -- Added `intro` - -## Anime : /pictures -- `image` -> `pictures` -- `pictures` is now an array with 2 items - - `large` - large version of the image - - `small` - small version of the image - -## Anime : /videos -- `episode` -> `episodes` - -## Anime : /stats -- `score_stats` -> `scores` - -## Anime : /forum -- `topic` -> `topics` - - `last_post` - - `date_relative` -> `date_posted` - - `date_posted` is now in ISO8601 - -## Anime : /moreinfo -- `more_info` -> `moreinfo` - - -## Manga -- Added `published['string']` -- Removed `published_string` -- `link_canonical` -> `url` -- `title_synonyms` is now an array -- `author` -> `authors` -- `serialization` -> `serializations` -- `genre` -> `genres` - -## Manga : /characters -- `character` -> `characters` - -## Manga : /news -- `news` -> `articles` -- `date` is now in ISO8601 -- Added `intro` - -## Manga : /pictures -- `image` -> `pictures` -- `pictures` is now an array with 2 items - - `large` - large version of the image - - `small` - small version of the image - -## Manga : /stats -- `score_stats` -> `scores` - -## Manga : /forum -- `topic` -> `topics` - - `last_post` - - `date_relative` -> `date_posted` - - `date_posted` is now in ISO8601 - -## Manga : /moreinfo -- `more_info` -> `moreinfo` - -## Character -- `link_canonical` -> `url` -- `nicknames` is now an array -- `voice_actor` -> `voice_actors` - -## Character : /pictures -- `image` -> `pictures` -- `pictures` is now an array with 2 items - - `large` - large version of the image - - `small` - small version of the image - -## Person -- `link_canonical` -> `url` -- `birthday` is now in ISO8601 -- `more` -> `about` -- `voice_acting_role` -> `voice_acting_roles` - - Added `role` -- `anime_staff_position` -> `anime_staff_positions` - - `role` -> `position` -- `published_manga` - - `role` -> `position` - -## Person : /pictures -- `image` -> `pictures` -- `pictures` is now an array with 2 items - - `large` - large version of the image - - `small` - small version of the image - -## Search -Search query as a URL segment is now depreciated. You have to pass the query via GET key `q`. -e.g `/search/anime?q=Fate/Zero` - -- `result` -> `results` -- `result_last_page` -> `last_page` - -## Search : /anime -- `result` - - Added `airing` (boolean) - - `description` -> `synopsis` - - Added `start_date` (ISO8601) - - Added `end_date` (ISO8601) - - Added `rated` - -## Search : /manga -- `result` - - Added `publishing` (boolean) - - `description` -> `synopsis` - - Added `start_date` (ISO8601) - - Added `end_date` (ISO8601) - - Added `chapters` (int) - -## Search : /people, /person -- `result` - - `nicknames` -> `alternative_names` - - `alternative_names` is now an array - -## Search : /character -- `result` - - `nicknames` -> `alternative_names` - - `alternative_names` is now an array - -## Season -- `season` -> `anime` - - `producer` -> `producers` - - `genre` -> `genres` - - `licensor` -> `licensors` - - `continued` -> `continuing` - - `airing_start` is now in ISO8601 - - `r18_plus` -> `r18` - -## Schedule -- Added `other` -- Added `unknown` -- `producer` -> `producers` -- `genre` -> `genres` -- `licensor` -> `licensors` -- `continued` -> `continuing` -- `airing_start` is now in ISO8601 -- `r18_plus` -> `r18` - - -## Top : Anime -- `airing_start` -> `start_date` -- `airing_end` -> `end_date` - -## Top : Manga -- `publishing_start` -> `start_date` -- `publishing_end` -> `end_date` \ No newline at end of file diff --git a/README.MD b/README.MD index 301952e..ba95a5c 100755 --- a/README.MD +++ b/README.MD @@ -1,187 +1,52 @@ ![Jikan](http://i.imgur.com/ctoJ3Jp.png) # Jikan - Unofficial MyAnimeList.net REST API -[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/jikan-me/jikan-rest.svg)](http://isitmaintained.com/project/jikan-me/jikan-rest "Average time to resolve an issue") [![Percentage of issues still open](http://isitmaintained.com/badge/open/jikan-me/jikan-rest.svg)](http://isitmaintained.com/project/jikan-me/jikan-rest "Percentage of issues still open") [![stable](https://img.shields.io/badge/PHP-^%207.4-blue.svg?style=flat)]() [![Discord Server](https://img.shields.io/discord/460491088004907029.svg?style=flat&logo=discord)](http://discord.jikan.moe/) +[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/jikan-me/jikan-rest.svg)](http://isitmaintained.com/project/jikan-me/jikan-rest "Average time to resolve an issue") [![Percentage of issues still open](http://isitmaintained.com/badge/open/jikan-me/jikan-rest.svg)](http://isitmaintained.com/project/jikan-me/jikan-rest "Percentage of issues still open") [![stable](https://img.shields.io/badge/PHP-^7.2.5-blue.svg?style=flat)]() [![Discord Server](https://img.shields.io/discord/460491088004907029.svg?style=flat&logo=discord)](https://discordapp.com/invite/4tvCr36) -Jikan is a REST API for [MyAnimeList.net](https://myanimelist.net). It scrapes the website to satisfy the need for an API - which MyAnimeList lacks. +Jikan is a REST API for [MyAnimeList.net](https://myanimelist.net). It scrapes the website to satisfy the need for some API functionality - that MyAnimeList lacks. -The raison d'être of Jikan is to assist developers easily get the data they need for their apps and projects without having to depend on the lackluster official API, unstable APIs, or sidetracking their projects to develop parsers. +The raison d'être of Jikan is to assist developers easily get the data they need for their apps and projects without having to depend on unstable APIs, or sidetracking their projects to develop parsers. The word _Jikan_ literally translates to _Time_ in Japanese (**時間**). And that's what this API saves you of. ;) - -**Notice**: Jikan does not support authenticated requests. You can not update your lists. - -## Index -- [Getting Started](#getting-started) - - [Requirements](#requirements) - - [🐳 Docker](#-docker) - - [Installation Prerequisites](#01-installation-prerequisites) - - [Installation](#02-installation) - - [Configuration](#03-configuration) - - [Ignition](#04-ignition) - - [Configuring Cache Driver](#05-configuring-how-jikan-caches-optional) (optional) - - [Configuring Cache Method](#06-configuring-how-jikan-handles-expired-cache-optional) (optional) - - [Configuring Supervisord](#configuring-supervisord) (optional) -- [Troubleshooting](#troubleshooting) -- [Artisan Commands](#artisan-commands) -- [Information](#information) -- [Wrappers](#wrappers) -- [Running Tests](#running-tests) -- [Backers](#backers) -- [Disclaimer](#disclaimer) - -## Getting Started - -### Requirements - -- PHP `^7.4` -- [Composer](https://getcomposer.org/download/) -- [Redis](https://redis.io) -- [Supervisord](http://supervisord.org/) (optional) - -#### 🐳 Docker - -If you don't want to install it yourself, you can use the [docker image](https://github.com/jikan-me/jikan-docker) - -### 01. Installation Prerequisites - -#### 01A. Linux - -This is specifically for Ubuntu, but other distributions should work similarly. -1. Install requirements: - - Add PHP related packages: `sudo add-apt-repository -y ppa:ondrej/php` - - If `add-apt-repository` is not installed, you can install it by doing `sudo apt install python-software-properties` or `sudo apt install software-properties-common` - - `sudo apt update && sudo apt upgrade` - - Install requirements: `sudo apt install curl git php redis unzip` - - Verify that PHP 7.4 is installed: `php -v` - - If not, install it by running `sudo apt install php7.4` and change the default PHP version with `sudo update-alternatives --set php /usr/bin/php7.4` - - Install the corresponding `php-xml` and `php-mbstring` packages for your version, e.g: - - PHP 7.4: `sudo apt install php7.4-xml php7.4-mbstring` - - Install composer: `curl -sS https://getcomposer.org/installer | sudo php -- --install-dir=/usr/local/bin --filename=composer` -2. Start the redis server: `sudo service redis start` - -#### 01B. Mac - -1. Install [brew](https://brew.sh/) -2. Install requirements: `brew install php composer redis` -3. Start the redis server: `brew services start redis` - -### 02. Installation - -1. `git clone https://github.com/jikan-me/jikan-rest.git` -2. `cd jikan-rest` -3. `cp .env.dist .env` -5. `composer install` (Make sure Jikan's directory has write permissions) - -### 03. Configuration - -You're able to configure Jikan through the `.env` file. -A few kernel commands are available from the project directory by running the `artisan` file. - -The first thing you need to do is generate an `APP_KEY`. - -1. `php artisan key:generate` -2. [Configure how Jikan caches](https://github.com/jikan-me/jikan-rest#05-configuring-how-jikan-caches-optional) -3. [Configure how Jikan handles expired cache](https://github.com/jikan-me/jikan-rest#06-configuring-how-jikan-handles-expired-cache-optional) - -### 04. Ignition - -`php artisan serve --port=8080` or `php -S localhost:8000 -t public` - -Jikan is now hosted on `http://localhost:8000/v3/` - -**Alternatively,** host it on Apache (or Nginx) - -Create a virtual host and point it to `/public`. Jikan supports Apache out of the box, you just need to create a virtual host and point it to `/public`, and enable the rewrite module for .htaccess (`sudo a2enmod rewrite`), and configure `/etc/apache/apache2.conf` by setting `AllowOverride None` to `AllowOverride All` for the `/var/www` directory. - -:information_source: If you wish to configure it for Nginx or anything else, you'll have to port the rewrite rules located at `public/.htaccess` - -### 05. Configuring how Jikan Caches (optional) -Jikan caches on file by default in `/storage/framework/cache`. So even if you don't change the caching method, Jikan will work out of the box. - -However, you can configure Jikan to cache on Redis instead: `php artisan cache:driver redis` - -Note: If you're currently running Jikan, you're required to stop it before running the above command. - -### 06. Configuring how Jikan handles expired cache (optional) -Jikan handles cache in the `legacy` manner out of the box. This method was used previously to update cache. +**Notice**: Jikan does not support authenticated requests. You can not update your lists. Use the official MyAnimeList API for this. -#### 06A. Cache Method: Legacy -When a cache expires, it gets deleted. So if you make a request that has an expired cache, your request will take longer as Jikan has to fetch and parse the new data from MyAnimeList again. +## Installation -#### 06B. Cache Method: Queue -This is a newly introduced caching method to the API, it's what the public API runs on as well. It requires some further setup. - -When a cache expires, it does not get deleted. Instead, if you make a request that has an expired cache, a job will be dispatched to the queue which handles updating the cache in the background. Therefore, the request will keep on providing stale cache until the job is complete and the cache is replaced with fresh data. - -This method provides zero delay, and is highly recommended if you have immense traffic coming your way. - -:information_source: Note: If you're currently running Jikan, you're required to stop it before running the above command. You're also required to clear any cache you've stored as well as anything on the Redis server. - -1. `php artisan cache:method queue` - -Next, you need to make sure that there's a service looking after the queue. This can be manually done by running a process through `php artisan queue:work --queue=high,low`. You can set the command to run on cron, nohup, etc. - -But a recommended way is to install Supervisor and have it handle the queue automatically. - -:information_source: Note: `--queue=high,low`; Jikan stores two types of queues; high priority and low priority. This depends on the type of request. You can check which request is considered to be high priority in the [JikanResponseHandler.php](https://github.com/jikan-me/jikan-rest/blob/master/app/Http/Middleware/JikanResponseHandler.php) middleware in the `HIGH_PRIORITY_QUEUE` array. - -:information_source: Note 2: Not all requests are queuable. Some are handled the `legacy` way. You can find out which ones in the [JikanResponseHandler.php](https://github.com/jikan-me/jikan-rest/blob/master/app/Http/Middleware/JikanResponseHandler.php) middleware in the `NON_QUEUEABLE` array. - -This reason for this is quite simple. User related requests such as anime/manga list can be frequently updated. They're cached by default for 5 minutes (you can change this in `.env`). But if they were to get queued for a cache update, it would take longer than 5 minutes because the update job would have to wait in line. So it skips the queue and is automatically updated on the request. This does mean a slight delay in fetching and parsing the fresh data from MyAnimeList. - -:information_source: Note 3: Note 1 & Note 2 are default behavior. You can obviously change them as per your needs. - -##### Configuring Supervisord - -###### Linux -1. Install supervisor - - Linux: `sudo apt install supervisor` - - Mac: `brew install supervisor` -2. `sudo cp conf/supervisor/jikan-worker.conf /etc/supervisor/conf.d` - - A default supervisor configureation file is available in this repo `conf/supervisor/jikan-worker.conf` - - Be sure to update to the correct directory in `jikan-worker.conf` for `command` and `stdout_logfile` to the directory of jikan! - -Example: If I install Jikan in `/var/www/jikan-is-installed-here`, you will have to adjust the following values in the `jikan-worker.conf` file. -``` -... -command=php /var/www/jikan-is-installed-here/artisan queue:work --queue=high,low -... -stdout_logfile=/var/www/jikan-is-installed-here/storage/logs/worker.log -stderr_logfile=/var/www/jikan-is-installed-here/storage/logs/worker.error.log -``` - -3. `sudo supervisorctl reread` -4. `sudo supervisorctl update` -5. `sudo supervisorctl start jikan-worker:*` - -###### Mac -1. Install Supervisor: `brew install supervisor` -2. `supervisord -c /usr/local/etc/supervisord.ini` -3. Copy `conf/supervisor/jikan-worker.conf` to `/usr/local/etc/supervisor.d/` -4. `brew services start supervisor` -5. `sudo supervisorctl update` -6. `sudo supervisorctl start jikan-worker:*` - - -## Troubleshooting -Please read the [troubleshooting guide](https://github.com/jikan-me/jikan-rest/blob/master/TROUBLESHOOTING.md). +### Manual installation +Please read the [manual installation guide](https://github.com/jikan-me/jikan-rest/wiki). For any additional help, join our [Discord server](http://discord.jikan.moe/). -## Artisan Commands -Please read the [commands guide](https://github.com/jikan-me/jikan-rest/blob/master/COMMANDS.MD). -For any additional help, join our [Discord server](http://discord.jikan.moe/). +### 🐳 Docker Installation +If you don't want to install it manually, you can use the [docker image](https://github.com/jikan-me/jikan-docker) -## Information +## Public REST API If you don't want to host your instance, there's a public API available. -- **[REST DOCUMENTATION](https://jikan.docs.apiary.io)** -- **[Apps/Projects using JikanREST](https://jikan.moe/showcase)** +- *[Apps/Projects using the REST API](https://jikan.moe/showcase)* + +### Documentation +Please view the [documentation](https://docs.api.jikan.moe/). +For any additional help, join our [Discord server](http://discord.jikan.moe/). ## Wrappers -See the list of wrappers [here](https://github.com/jikan-me/jikan#wrappers) +| Language | Wrappers | +|------------|----------| +| JavaScript | [JikanJS](https://github.com/zuritor/jikanjs) by Zuritor | +| Java | [Jikan4java](https://github.com/Doomsdayrs/Jikan4java) by Doomsdayrs
[reactive-jikan](https://github.com/SandroHc/reactive-jikan) by Sandro Marques
[Jaikan](https://github.com/ShindouMihou/Jaikan) by ShindouMihou | +| Python | [JikanPy](https://github.com/abhinavk99/jikanpy) by Abhinav Kasamsetty | +| Node.js | [jikan-node](https://github.com/xy137/jikan-node) by xy137
[jikan-nodejs](https://github.com/ribeirogab/jikan-nodejs) by ribeirogab | +| TypeScript | [jikants](https://github.com/Julien-Broyard/jikants) by Julien Broyard
[jikan-client](https://github.com/javi11/jikan-client) by Javier Blanco | +| PHP | [jikan-php](https://github.com/janvernieuwe/jikan-jikanPHP) by Jan Vernieuwe | +| .NET | [Jikan.net](https://github.com/Ervie/jikan.net) by Ervie | +| Elixir | [JikanEx](https://github.com/seanbreckenridge/jikan_ex) by Sean Breckenridge | +| Go | [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 | +| Kotlin | [JikanKt](https://github.com/GSculerlor/JikanKt) by Ganedra Afrasya | + +[Add your wrapper here](https://github.com/jikan-me/jikan-rest/edit/master/readme.md) ## Running Tests @@ -190,19 +55,18 @@ See the list of wrappers [here](https://github.com/jikan-me/jikan#wrappers) Note: Tests may fail due to rate limit from MyAnimeList (HTTP 429) --- - -# Backers +## Backers A huge thank you to all our Patrons! 🙏 This project wouldn't be running without your support. We have a free [REST API service](https://jikan.moe), if you wish to support us you can [become a Patron!](https://patreon.com/jikan) -## Sugoi (すごい) Patrons +### Sugoi (すごい) Patrons - [Jared Allard (jaredallard)](https://github.com/jaredallard) - [hugonun (hug_onun)](https://twitter.com/hug_onun) -## Patrons +### Patrons - Aaron Treinish - Aika Fujiwara diff --git a/apiary.apib b/apiary.apib deleted file mode 100644 index 3df1131..0000000 --- a/apiary.apib +++ /dev/null @@ -1,809 +0,0 @@ -FORMAT: 1A -HOST: https://api.jikan.moe/v3 - -# Jikan -[Jikan](https://jikan.moe) is an **Unofficial** MyAnimeList API. It scrapes the website to satisfy the need for an API - which MyAnimeList lacks. - -The word Jikan literally translates to Time in Japanese (時間). And that's what this API saves you of. ;) - -Notice: Jikan does not support authenticated requests. You can not update your lists. - -⚡ Jikan is powered thanks to all its [backers](https://github.com/jikan-me/jikan#sugoi-%E3%81%99%E3%81%94%E3%81%84-backers)! 🙏 [[Become a backer]](https://patreon.com/jikan) - - -## -**API Path:** `https://api.jikan.moe/v3` - -**API Version**: `v3.4` - -[Status](https://status.jikan.moe) | [Report an Issue](https://github.com/jikan-me/jikan-rest/issues/new) | **[Discord](http://discord.jikan.moe)** - - -# Information - -## Links -- [Jikan.moe](https://jikan.moe) -- [About](https://jikan.moe/about) -- [Stuff using Jikan](https://jikan.moe/showcase) - - -## Wrappers - -Wrappers are available in more than 10 different languages, see the [Wrapper List](https://github.com/jikan-me/jikan#wrappers) - -## Rate Limiting -Daily Limit: **Unlimited** - -- **30 requests** / minute -- **2 requests** / second - -**Note: Cached requests are NOT throttled** - -## Bulk Requests -This API serves as a purpose for apps/projects that are user based and make a nominal amount of requests. - -⚠️ If you're using the service for the sake of populating data/making your own database; -- You are breaching [MyAnimeList's Terms Of Service](https://myanimelist.net/membership/terms_of_use). **You are responsible for what you're doing.** -- **You MUST use a delay of 4 (FOUR) SECONDS between each request** -- Requesting from multiple servers/IPs is being cheeky and is **NOT** allowed -- **ABUSING THE API WILL RESULT IN GETTING BLOCKED FROM THE SERVICE** - -If you're not comfortable being that restrictive, consider setting up your own Jikan REST API - It's super easy. -- [Jikan REST API - GitHub](https://github.com/jikan-me/jikan-rest) -- [Jikan REST API - Docker](https://github.com/jikan-me/jikan-docker) - - -## Disclaimer -- Jikan is not affiliated with MyAnimeList.net -- Jikan is a **free**, open-source API. Use it responsibly! - - -## JSON Notes -- Any property (except arrays) whose value does not exist or is undetermined, will be `null` -- Any array property whose value does not exist or is undetermined, will be **empty** -- Any `score` property whose value does not exist or is undetermined, will be `0` -- All dates and timestamps are returned in **ISO8601** format and in **UTC** - -## Caching - -By "caching", we refer to the data parsed from MyAnimeList that is cached temporarily on our servers for better performance. - -All requests by default are cached for **24 hours** except for a few API endpoints which have their own unique cache expiry time. - - -Request | Cache TTL -:-------------- | :-------- -All (Default) | 24 hours -Meta | 5 minutes -User | 5 minutes -Search | 120 hours (5 days) - -The following Response Headers will detail cache information - -Header | Remarks -:--------------------- | :-------- -`Expires` | Expiry timestamp for the cache -`X-Request-Cached` | (boolean) Is the request cached? -`X-Request-Cache-Ttl` | (integer) Cache Time-To-Live in seconds - - -**FAQ: Why is `X-Request-Cache-Ttl` negative?** - -If the cache expires, it queues a job in the background to update the cache. -So you're getting stale cache until the cache update completes. - - -## Allowed HTTP(s) requests -
-GET: All requests are done via GET
-
- -**The Jikan REST API does not provide authenticated requests for MyAnimeList.** -This means you can not use it to update your anime/manga lists. - -**Reasons:** -- Why on earth would you send your credentials to a 3rd party API? -- MyAnimeList will block our IP after multiple failed login attempts - -However, do not fret. This is possible via their own website. [Read the Specification](https://github.com/jikan-me/jikan-auth/blob/master/SPECIFICATION.md) - -Furthermore, [JikanAuth](https://github.com/jikan-me/jikan-auth) is a PHP API which you can use to update your lists - it implements the **Specification** above. So feel free to come up with your own client-side solution. - - -# HTTP Response -- 200 `OK` - the request was successful. -- 304 `Not Modified` - You have the latest data -- 400 `Bad Request` - You’ve made an invalid request -- 404 `Not Found` - Resource not found, i.e MyAnimeList responded with a 404 -- 405 `Method Not Allowed` - requested method is not supported for resource -- 429 `Too Many Requests` - You are being rate limited or Jikan is being rate limited by MyAnimeList (either is specified in the error message) -- 500 `Internal Server Error` - Something is not working on our end, please open a Github issue by clicking on the generated `report_url` -- 503 `Service Unavailable` - Something is not working on MyAnimeList’s end. MyAnimeList is either down/unavailable or is refusing to connect - - -# JSON Error Response -This is a typical error response -``` -{ - "status": 404, - "type": "BadResponseException", - "message": "Resource does not exist" - "error": "Something Happened" -} -``` - -Property | Remarks -:-------------- | :-------- -`status` | HTTP Status returned -`type` | `Exception` generated from the PHP API -`message` | Appropriate error message from the REST API -`error` | Error response from the PHP API -`report_url` (fatal errors only) | Clicking on this would redirect you to a generated GitHub Issue - - - -# Cache Validation -- All requests return a `ETag` header which is an md5 hash of the content. - -- You can use this hash to verify if there's new or updated content by supplying it as the value for the `If-None-Match` header. - -- You will get a `304` HTTP response if your value and the data on Jikan matches. - -- If there's new/updated content, you'll get a normal `200` HTTP response with the updated content. - -### How To Use -1. Use the `ETag` value as a header value for `If-None-Match` for future requests -2. If the content has changed, you will get a `304 - Not Modified` header response, otherwise `200 - OK` - - -![Cache Validation](https://i.imgur.com/925ozVn.png "Cache Validation") - - -## Anime [/anime/{id}/{request}/{parameter}] -A single anime object with all its details - -**Endpoint Path:** `/anime/{id}(/request)` - -### Requests -| Request | Parameter | Description | -| ------------- | ------------- | ------------- | -| `/` | N/A | Resource object with all it's details | -| `/characters_staff` | N/A | List of character and staff members | -| `/episodes` | Page number (integer) | List of episodes | -| `/news` | N/A | List of Related news | -| `/pictures` | N/A | List of Related pictures | -| `/videos` | N/A | List of Promotional Videos & episodes (if any) | -| `/stats` | N/A | Related statistical information | -| `/forum` | N/A | List of Related forum topics | -| `/moreinfo` | N/A | A string of more information (if any) | -| `/reviews` | Page number (integer) | List of Reviews written by users | -| `/recommendations` | N/A | List of Recommendations and their weightage made by users | -| `/userupdates` | Page number (integer) | List of the latest list updates made by users | - - -#### Remarks - -##### `/episodes` -- The field `episodes_last_page` will tell you the last page of the paginated episodes list. - -- The episodes page on MyAnimeList get paginated after 100 episodes. If there's an anime with more than 100 episodes, you'll have to use the parameter. - -##### `/reviews` -- Only 20 items are shown per page for reviews - -### Examples -- `/anime/1/characters_staff` - Returns the list of characters and staff -- `/anime/1/episodes` - Defaults to the 1st page -- `/anime/1/episodes/1` - Same as above -- `/anime/1/episodes/2` - Returns 2nd page if there's any - - -### Fetch Resource [GET] - -+ Parameters - + id (required, Number, `1`) ... MyAnimeList ID of the anime - + request (optional, String, `episodes`) ... More details such as characters, staff, episodes - + parameter (optional, Number, `2`) ... Anime with more than 100 episodes are paginated, hence this parameter is required. - -+ Response 200 (application/json) - - [ - - ] - - -## Manga [/manga/{id}/{request}] -A single manga object with all its details -### Requests -| Request | Parameter | Description | -| ------------- | ------------- | ------------- | -| characters | N/A | Fetches the list of characters & staff members of the manga | -| news | N/A | News related to the item | -| pictures | N/A | Pictures related to the item | -| stats | N/A | Statistical information related to the item | -| forum | N/A | Forum topics related to the item | -| moreinfo | N/A | More info related to the item | -| reviews | Page number (integer) | Reviews written by users | -| recommendations | N/A | Recommendations and their weightage made by users | -| userupdates | Page number (integer) | Latest list updates made by users | - -### Example Calls -- `/manga/1/characters` Returns the list of characters and staff - - -#### Remarks - -- Only 20 items are shown per page for reviews - -### Manga Request Example+Schema [GET] - -+ Parameters - + id (required, Number, `1`) ... Returns the Character details from that the ID - + request (optional, String, `characters`) ... More details such as characters - -+ Response 200 (application/json) - - [ - - ] - -## Person [/person/{id}/{request}] -A single person object with all its details -### Requests -| Request | Parameter | Description | -| ------------- | ------------- | ------------- | -| pictures | N/A | Pictures related to the item | - -### Person Request Example+Schema [GET] - -+ Parameters - + id (required, Number, `1`) ... Returns the Person details from that the ID - + request (optional, String, `pictures`) ... Pictures related to the item - -+ Response 200 (application/json) - - [ - - ] - -## Character [/character/{id}/{request}] -A single character object with all its details -### Requests -| Request | Parameter | Description | -| ------------- | ------------- | ------------- | -| pictures | N/A | Pictures related to the item | - -### Character Request Example+Schema [GET] - - -+ Parameters - + id (required, Number, `1`) ... Returns the Character details from that the ID - + request (optional, String, `pictures`) ... Pictures related to the item - -+ Response 200 (application/json) - - [ - - ] - - -## Search [/search/{type}?q=Fate/Zero&page=1] -Search results for the query - -**NOTE: MyAnimeList only processes queries with a minimum of 3 letters.** However, the search function can be used without `q`! Check examples below for more details. - -### Parameters -| Parameter | Argument | Description | -| ------------- | ------------- | ------------- | -| type | anime, manga, person, character | Specify where to search | -| page | INTEGER | Page number of the results | - -### Advanced Search Parameters (Anime & Manga) -**Note:** These are search filters which have to be passed as GET `key=value` -| Parameter | Argument | Description | -| ------------- | ------------- | ------------- | -| q | STRING | For UTF8 characters, percentage encoded and queries including back slashes | -| page | INTEGER | Page number | -| type | **See Enums Below** | Filter type of results | -| status | **See Enums Below** | Filter status of results | -| rated | **See Enums Below** | Filter age rating of results | -| genre | **See Enums Below** | Filter by genre ID(s) | -| score | FLOAT : 0.0-10.0 | Filter score of results | -| start_date | `yyyy-mm-dd` | Filter start date of results | -| end_date | `yyyy-mm-dd` | Filter end date of results | -| genre_exclude | boolean : 0/1 | To exclude/include the `genre` you added in your request | -| limit | INTEGER | Limits item results to the number specified | -| order_by | **See Enums Below** | Order results with respect to a property | -| sort | **See Enums Below** | Sort `order_by` (Default is `descending`) | -| producer | INTEGER | MAL ID of the producer | -| magazine | INTEGER | MAL ID of the magazine | -| letter | UTF8 Character | Search anime or manga by the letter/character it starts with | - - -#### Enums -##### `type` -| Anime Types | Manga Types | -| :------------ | :---------- | -| `tv` | `manga` | -| `ova` | `novel` | -| `movie` | `oneshot` | -| `special` | `doujin` | -| `ona` | `manhwa` | -| `music` | `manhua` | - -##### `status` -| Anime Status | Manga Status | -| :------------ | :---------- | -| `airing` | `publishing` | -| `completed` | `completed` | -| `complete` (alias) | `complete` (alias) | -| `to_be_aired` | `to_be_published` | -| `tba` (alias) | `tbp` (alias) | -| `upcoming` (alias) | `upcoming` (alias) | - -##### `rated` -Anime ratings are based on MyAnimeList's rating system. -Read more about them [here](https://myanimelist.net/info.php?go=mpaa) - -| Anime Search | Remarks | -| :------------ | :----- | -| `g` | **G** - All Ages | -| `pg` | **PG** - Children | -| `pg13` | **PG-13** - Teens 13 or older | -| `r17` | **R** - 17+ recommended (violence & profanity) | -| `r` | **R+** - Mild Nudity (may also contain violence & profanity) | -| `rx` | **Rx** - Hentai (extreme sexual content/nudity) | - - -##### `order_by` -| Anime Search | Manga Search | -| :------------ | :---------- | -| `title` | `title` | -| `start_date` | `start_date` | -| `end_date` | `end_date` | -| `score` | `score` | -| `type` | `type` | -| `members` | `members` | -| `id` | `id` | -| `episodes` | `chapters` | -| `rating` | `volumes` | - -##### `sort` -| Anime & Manga Sort | -| :------------ | -| `ascending` | -| `asc` (alias) | -| `descending` | -| `desc` (alias) | - -##### `genre` -| Anime Genre | Manga Genre | -| :------------ | :---------- | -| **Action:** `1` | **Action:** `1` | -| **Adventure:** `2` | **Adventure:** `2` | -| **Cars:** `3` | **Cars:** `3` | -| **Comedy:** `4` | **Comedy:** `4` | -| **Avante Garde:** `5` | **Avante Garde:** `5` | -| **Demons:** `6` | **Demons:** `6` | -| **Mystery:** `7` | **Mystery:** `7` | -| **Drama:** `8` | **Drama:** `8` | -| **Ecchi:** `9` | **Ecchi:** `9` | -| **Fantasy:** `10` | **Fantasy:** `10` | -| **Game:** `11` | **Game:** `11` | -| **Hentai:** `12` | **Hentai:** `12` | -| **Historical:** `13` | **Historical:** `13` | -| **Horror:** `14` | **Horror:** `14` | -| **Kids:** `15` | **Kids:** `15` | -| **Martial Arts:** `17` | **Martial Arts:** `17` | -| **Mecha:** `18` | **Mecha:** `18` | -| **Music:** `19` | **Music:** `19` | -| **Parody:** `20` | **Parody:** `20` | -| **Samurai:** `21` | **Samurai:** `21` | -| **Romance:** `22` | **Romance:** `22` | -| **School:** `23` | **School:** `23` | -| **Sci Fi:** `24` | **Sci Fi:** `24` | -| **Shoujo:** `25` | **Shoujo:** `25` | -| **Girls Love:** `26` | **Girls Love:** `26` | -| **Shounen:** `27` | **Shounen:** `27` | -| **Boys Love:** `28` | **Boys Love**: `28` | -| **Space:** `29` | **Space:** `29` | -| **Sports:** `30` | **Sports:** `30` | -| **Super Power:** `31` | **Super Power:** `31` | -| **Vampire:** `32` | **Vampire:** `32` | -| **Harem:** `35` | **Harem:** `35` | -| **Slice Of Life:** `36` | **Slice Of Life:** `36` | -| **Supernatural:** `37` | **Supernatural:** `37` | -| **Military:** `38` | **Military:** `38` | -| **Police:** `39` | **Police:** `39` | -| **Psychological:** `40` | **Psychological:** `40` | -| **Suspense:** `41` | **Seinen:** `41` | -| **Seinen:** `42` | **Josei:** `42` | -| **Josei:** `43` | **Doujinshi:** `43` | -| | **Gender Bender:** `44` | -| | **Suspense:** `45` | -| **Award Winning**: `46` | **Award Winning**: `46` | -| **Gourmet**: `47` | **Gourmet**: `47` | -| **Work Life**: `48` | **Work Life**: `48` | -| **Erotica**: `49` | **Erotica**: `49` - -#### Examples -- `/search/manga?q=Grand%20Blue&page=1` - Search manga for 'Grand Blue' -- `/search/anime?q=Fate/Zero&page=1` - Search anime for 'Fate/Zero' -- `/search/people/?q=Sawashiro&limit=3` - Search people for 'Sawashiro', limit to 3 results -- `/search/anime?q=Boku&page=1&genre=12&genre_exclude=0` - Filter out NSFW entries by using the `genre` and `genre_exclude` parameters - -In many cases, if you want to filter results by a condition, you can provide an empty query (`q=`) with the `order_by` and `sort` parameters: - -- `/search/anime?q=&order_by=members&sort=desc&page=1` - Search for all anime, ordered by popularity (members, descending) -- `/search/anime?q=&page=1&genre=1,10&order_by=start_date&sort=desc` - Search for all anime which have both Genre 1 and 10 (Action, Fantasy), order by most recently released - -#### Remarks -- The `last_page` field is the same as what is avaiable on the MAL Search page, its often paginated to 20 pages if there are too many results. - -### Search Request Example+Schema [GET] - - -+ Parameters - + type (required, String, `anime`) ... Returns result from anime search - -+ Response 200 (application/json) - - [ - - ] - -## Season [/season/{year}/{season}] -Anime of the specified season - -**Note:** Not using parameters will return the current season's anime listing - -### Parameters -| Parameter | Argument | Description | -| ------------- | ------------- | ------------- | -| year | Integer: Year | Specify the year | -| season | `summer` `spring` `fall` `winter` | Specify the season | - -### Season Request Example+Schema [GET] - -+ Parameters - + year (optional, Integer, `2018`) ... Returns anime of the year - + season (optional, String, `winter`) ... Returns anime of the season - -+ Response 200 (application/json) - - [ - - ] -## Season Archive [/season/archive] -All the years & their respective seasons that can be parsed from MyAnimeList - -### Season Archive Request Example+Schema [GET] - -+ Response 200 (application/json) - - [ - - ] - - -## Season Later [/season/later] -Anime that have been announced for the upcoming seasons - -### Season Later Request Example+Schema [GET] - -+ Response 200 (application/json) - - [ - - ] - -## Schedule [/schedule/{day}] -Anime schedule of the week or specified day - -**Note:** If you don't pass the `day` parameter, it'll return the schedule for **all** days of the week -### Parameters -| Parameter | Argument | Description | -| ------------- | ------------- | ------------- | -| day (optional) | `monday` `tuesday` `wednesday` `thursday` `friday` `saturday` `sunday`, `other` **(v3)**, `unknown` **(v3)** | Anime scheduled for that specific day | - -### Schedule Request Example+Schema [GET] - -+ Parameters - + day (optional, String, `monday`) ... Returns scheduled anime of that specific day - -+ Response 200 (application/json) - - [ - - ] - -## Top [/top/{type}/{page}/{subtype}] -Top items on MyAnimeList - -**Note:** `subtype` returns a filtered top list of a type `type` item. For example, the top Anime (type) movies (subtype) -**Note 2:** `subtype` is only for `anime` and `manga` types. -**Note 3:** Date properties are returned in string as they only consist of the month and year - which is not appropriate for ISO8601 - -### Parameters -| Parameter | Argument | Description | -| ------------- | ------------- | ------------- | -| type | `anime` `manga`, `people` (v3+), `characters` (v3+) | Top items of this type | -| page (optional) | INTEGER | The Top page on MyAnimeList is paginated offers 50 items per page | -| subtype (optional) | **Anime:** `airing` `upcoming` `tv` `movie` `ova` `special` **Manga:** `manga` `novels` `oneshots` `doujin` `manhwa` `manhua` **Both:** `bypopularity` `favorite` | - -### Top Request Example+Schema [GET] - -+ Parameters - + type (required, String, `anime`) ... Returns top items of this type - + page (optional, Integer, `1`) ... Pagination support - + subtype (optional, String, `upcoming`) ... Returns top items of this type filtered by their subtypes - -+ Response 200 (application/json) - - [ - - ] - -## Genre [/genre/{type}/{genre_id}/{page}] -Anime/Manga items of the genre - -**Note:** Genres with their respective IDs are listed [here]() -### Parameters -| Parameter | Argument | Description | -| ------------- | ------------- | ------------- | -| type | `anime` `manga` | Genre of this type | -| genre_id | INTEGER | Genre ID from MyAnimeList - [Genre Mapping]() | -| page (optional) | | - -### Genre Request Example+Schema [GET] - -+ Parameters - + type (required, String, `anime`) ... Returns anime/manga items of this genre - + genre_id (optional, Integer, `1`) ... Genre ID - + page (optional, Integer, `1`) ... Pagination - -+ Response 200 (application/json) - - [ - - ] - -## Producer [/producer/{producer_id}/{page}] -Anime by this Producer/Studio/Licensor - -### Parameters -| Parameter | Argument | Description | -| ------------- | ------------- | ------------- | -| producer_id | INTEGER | Producer ID from MyAnimeList | -| page (optional) | | - -### Producer Request Example+Schema [GET] - -+ Parameters - + producer_id (optional, Integer, `1`) ... Producer ID - + page (optional, Integer, `1`) ... Pagination - -+ Response 200 (application/json) - - [ - - ] - -## Magazine [/magazine/{magazine_id}/{page}] -Manga by this Magazine/Serializer/Publisher - -### Parameters -| Parameter | Argument | Description | -| ------------- | ------------- | ------------- | -| magazine_id | INTEGER | Magazine ID from MyAnimeList | -| page (optional) | | - -### Magazine Request Example+Schema [GET] - -+ Parameters - + magazine_id (optional, Integer, `1`) ... Magazine ID - + page (optional, Integer, `1`) ... Pagination - -+ Response 200 (application/json) - - [ - - ] - -## User [/user/{username}/{request}/{argument}] -User related data - -**Note:** About is returned in HTML as MyAnimeList allows custom "about" sections for users that can consist of images, formatting, etc. - -**Note 2:** Anime & Manga Lists are paginated. Only 300 items are returned per page. - -### Parameters -| Parameter | Argument | Description | -| ------------- | ------------- | ------------- | -| username | string | Username on MyAnimeList | -| request | `profile`, `history`, `friends`, `animelist`, `mangalist` | -| data (optional) | Additional data for the requests | - -#### Data -| Data | Argument | Description | -| ------------- | ------------- | ------------- | -| history | `anime`, `manga` | Returns both combined if neither are passed | -| friends | INTEGER | Pagination support; Status 404 if there's no friends on the page | -| animelist | **See Enums Below** | -| mangalist | **See Enums Below** | - -#### User List Filter -| Anime List `/animelist` | Manga List `/mangalist` | -| :------------ | :---------- | -| `/` | `/` | -| `/all` (alias) | `/all` (alias) | -| `/watching` | `/reading` | -| `/completed` | `/completed` | -| `/onhold` | `/onhold` | -| `/dropped` | `/dropped` | -| `/plantowatch` | `/plantoread` | -| `/ptw` (alias) | `/ptr` | - -### Advanced User List Parameters (Anime & Manga) -**Note:** These are search filters which have to be passed as GET `key=value` -| Parameter | Argument | Description | -| ------------- | ------------- | ------------- | -| `search` | STRING | Return items in your list matching the string | -| `q` (alias) | STRING | Return items in your list matching the string | -| `page` (alias) | INTEGER | Pass page number as a `key=value` | -| `sort` | **See Enums Below** | Sort `order_by` (Default is `descending`) | - -### Advanced User List Parameters (ANIME) -| Parameter | Argument | Description | -| ------------- | ------------- | ------------- | -| `order_by` | **See Enums Below** | Order items with respect to a property | -| `order_by2` | **See Enums Below** | Order items with respect to a second property | -| `aired_from` | `yyyy-mm-dd` | Filter Anime that have aired from this date | -| `aired_to` | `yyyy-mm-dd` | Filter Anime that have aired till this date | -| `producer` | Integer | Filter Anime by this Producer ID | -| `year` | Integer: Year | Filter anime from a year | -| `season` | `summer` `spring` `fall` `winter` | Filter anime from a season (require `year`) | -| `airing_status` | **See Enums Below** | Filter Anime with a status | - -### Advanced User List Parameters (MANGA) -| Parameter | Argument | Description | -| ------------- | ------------- | ------------- | -| `order_by` | **See Enums Below** | Order items with respect to a property | -| `order_by2` | **See Enums Below** | Order items with respect to a second property | -| `published_from` | `yyyy-mm-dd` | Filter Manga that have published from this date | -| `published_to` | `yyyy-mm-dd` | Filter Manga that have published till this date | -| `magazine` | Integer | Filter Manga by this Magazine ID | -| `publishing_status` | **See Enums Below** | Filter Manga with a status | - -Regarding `yyyy-mm-dd` dates, you can search for only the year or only the year and the month as well. -Just pass it as `yyyy-00-00` or `yyyy-mm-00`. e.g `2018-00-00`, `2018-12-00` - - -#### Enums -##### `order_by` & `order_by2` -| Anime List | Manga List | -| :------------ | :---------- | -| `title` | `title` | -| `finish_date` | `finish_date` | -| `start_date` | `start_date` | -| `score` | `score` | -| `last_updated` | `last_updated` | -| `type` | `type` | -| `rated` | `` | -| `rewatch` | `` | -| `rewatch_value` (alias) | `` | -| `priority` | `priority` | -| `progress` | `progress` (`chapters_read`) | -| `episodes_watched` (alias) | `chapters_read` (alias) | -| | `volumes_read` | -| `storage` | `` | -| `air_start` | `publish_start` | -| `air_end` | `publish_end` | -| `status` | `status` | - -##### `sort` -| Anime & Manga Sort | -| :------------ | -| `ascending` | -| `asc` (alias) | -| `descending` | -| `desc` (alias) | - -##### `airing_status` & `publishing_status` -| Anime Airing Status | Manga Publishing Status | -| :------------ | :------------ | -| `airing` | `publishing` | -| `finished` | `finished` | -| `complete` (alias) | `complete` (alias) | -| `to_be_aired` | `to_be_published` | -| `not_yet_aired` (alias) | `not_yet_published` (alias) | -| `tba` (alias) | `tbp` (alias) | -| `nya` (alias) | `nyp` (alias) | - - -#### Examples -- `/user/nekomata1037` - Parses Profile -- `/user/nekomata1037/profile` (alias) -- `/user/nekomata1037/history` - Parses user history (anime+manga) -- `/user/nekomata1037/history/anime` - Parses user history (anime only) -- `/user/nekomata1037/friends` - Parses user friends - -The request below will return 404 because I don't have that many friends on MAL to generate a second page. -`/user/nekomata1037/friends/2` - Parses user friends (from page 2) - - -**Anime & Manga Lists** - -Lists are paginated (300 items per page). - -- `/user/nekomata1037/animelist/all` - All anime in user list -- `/user/nekomata1037/animelist/all/2` - Page 2 -- `/user/nekomata1037/mangalist/reading` - Manga that I'm currently reading - -### User Request Example+Schema [GET] -+ Parameters - + username (required, string, `Nekomata1037`) ... Username on MyAnimeList - + request (optional, string, `history`) ... Request - + argument (optional, string, `anime`) ... Request argument - -+ Response 200 (application/json) - - [ - - ] - - -## Club [/club/{id}/{request}] -A single club object with all its details -### Requests -| Request | Parameter | Description | -| ------------- | ------------- | ------------- | -| members | Page (INTEGER) | Fetches list of club members | - - -### Example Calls -- `/club/1` // Returns club information -- `/club/1/members/1` // Returns list of club members - - -#### Remarks - -- Only 35 items are shown per page for members - -### Club Request Example+Schema [GET] - -+ Parameters - + id (required, Number, `1`) ... Returns the Club details from that the ID - + request (optional, String, `members`) ... Return club members - -+ Response 200 (application/json) - - [ - - ] - - -## Meta [/meta/{request}/{type}/{period}] -Requests related to meta information regarding the Jikan REST Instance. -Such as the most requested endpoints for a specific period, or just status on the REST API. - -### Parameters -| Parameter | Argument | Description | -| ------------- | ------------- | ------------- | -| request | `requests` `status` | | -| type | `anime` `manga` `character` `person` `search` `top` `schedule` `season` | This is only for the `requests` endpoint | -| period | `today` `weekly` `monthly` | This is only for the `requests` endpoint | -| offset | int | 1,000 requests are shown per page, you can use the offset to show more | - -### Meta Request Example+Schema [GET] - -+ Parameters - + request (required, String, `requests`) ... - + type (required, String, `anime`) ... - + period (required, String, `today`) ... - -+ Response 200 (application/json) - - [ - - ] \ No newline at end of file diff --git a/app/Anime.php b/app/Anime.php new file mode 100644 index 0000000..754fdfc --- /dev/null +++ b/app/Anime.php @@ -0,0 +1,134 @@ +attributes['season'] = $this->getSeasonAttribute(); + } + + public function getSeasonAttribute() + { + $premiered = $this->attributes['premiered']; + + if (empty($premiered) + || is_null($premiered) + || !preg_match('~(Winter|Spring|Summer|Fall|)\s([\d+]{4})~', $premiered) + ) { + return null; + } + + $season = explode(' ', $premiered)[0]; + return strtolower($season); + } + + public function setYearAttribute($value) + { + $this->attributes['year'] = $this->getYearAttribute(); + } + + public function getYearAttribute() + { + $premiered = $this->attributes['premiered']; + + if (empty($premiered) + || is_null($premiered) + || !preg_match('~(Winter|Spring|Summer|Fall|)\s([\d+]{4})~', $premiered) + ) { + return null; + } + + return (int) explode(' ', $premiered)[1]; + } + + public function setBroadcastAttribute($value) + { + $this->attributes['year'] = $this->getBroadcastAttribute(); + } + + public function getBroadcastAttribute() + { + $broadcastStr = $this->attributes['broadcast']; + + if (!preg_match('~(.*) at (.*) \(~', $broadcastStr, $matches)) { + return [ + 'day' => null, + 'time' => null, + 'timezone' => null, + 'string' => $broadcastStr + ]; + } + + if (preg_match('~(.*) at (.*) \(~', $broadcastStr, $matches)) { + return [ + 'day' => $matches[1], + 'time' => $matches[2], + 'timezone' => 'Asia/Tokyo', + 'string' => $broadcastStr + ]; + } + + return [ + 'day' => null, + 'time' => null, + 'timezone' => null, + 'string' => null + ]; + } + + 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/Character.php b/app/Character.php new file mode 100644 index 0000000..60f8fcd --- /dev/null +++ b/app/Character.php @@ -0,0 +1,63 @@ +attributes['member_favorites']; + } + + public static function scrape(int $id) + { + $data = app('JikanParser')->getCharacter(new CharacterRequest($id)); + return json_decode( + app('SerializerV4') + ->serialize($data, 'json'), + true + ); + } +} \ No newline at end of file diff --git a/app/Club.php b/app/Club.php new file mode 100644 index 0000000..b7300bd --- /dev/null +++ b/app/Club.php @@ -0,0 +1,55 @@ +getClub(new ClubRequest($id)); + + return json_decode( + app('SerializerV4') + ->serialize($data, 'json'), + true + ); + } +} \ No newline at end of file diff --git a/app/Console/Commands/Indexer/AnimeIndexer.php b/app/Console/Commands/Indexer/AnimeIndexer.php new file mode 100644 index 0000000..8212d52 --- /dev/null +++ b/app/Console/Commands/Indexer/AnimeIndexer.php @@ -0,0 +1,172 @@ +option('failed') ?? false; + $resume = $this->option('resume') ?? false; + $reverse = $this->option('reverse') ?? false; + $delay = $this->option('delay') ?? 3; + $index = $this->option('index') ?? 0; + + $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"); + + if ($failed && Storage::exists('indexer/indexer_anime.save')) { + $this->ids = $this->loadFailedMalIds(); + } + + if (!$failed) { + $this->ids = $this->fetchMalIds(); + } + + // start from the end + if ($reverse) { + $this->ids = array_reverse($this->ids); + } + + // Resume + if ($resume && Storage::exists('indexer/indexer_anime.save')) { + $index = (int)Storage::get('indexer/indexer_anime.save'); + + $this->info("Resuming from index: {$index}"); + } + + // check if index even exists + if ($index > 0 && !isset($this->ids[$index])) { + $index = 0; + $this->warn('Invalid index; set back to 0'); + } + + // initialize and index + Storage::put('indexer/indexer_anime.save', 0); + + echo "Loading MAL IDs\n"; + $count = count($this->ids); + $failedIds = []; + $success = []; + + echo "{$count} entries available\n"; + for ($i = $index; $i <= ($count - 1); $i++) { + $id = $this->ids[$i]; + + $url = env('APP_URL') . "/v4/anime/{$id}"; + + echo "Indexing/Updating " . ($i + 1) . "/{$count} {$url} [MAL ID: {$id}] \n"; + + try { + $response = json_decode(file_get_contents($url), true); + + if (isset($response['error']) && $response['status'] != 404) { + echo "[SKIPPED] Failed to fetch {$url} - {$response['error']}\n"; + $failedIds[] = $id; + Storage::put('indexer/indexer_anime.failed', json_encode($failedIds)); + } + + sleep($delay); + } catch (\Exception $e) { + echo "[SKIPPED] Failed to fetch {$url}\n"; + $failedIds[] = $id; + Storage::put('indexer/indexer_anime.failed', json_encode($failedIds)); + } + + $success[] = $id; + Storage::put('indexer/indexer_anime.save', $i); + } + + Storage::delete('indexer/indexer_anime.save'); + + echo "---------\nIndexing complete\n"; + echo count($success) . " entries indexed or updated\n"; + echo count($failedIds) . " entries failed to index or update. Re-run with --failed to requeue failed entries only\n"; + } + + /** + * @return array + * @url https://github.com/seanbreckenridge/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"); + + $ids = json_decode( + file_get_contents('https://raw.githubusercontent.com/seanbreckenridge/mal-id-cache/master/cache/anime_cache.json'), + true + ); + + $this->ids = $ids['sfw'] + $ids['nsfw']; // merge + Storage::put('indexer/anime_mal_id.json', json_encode($this->ids)); + + return json_decode(Storage::get('indexer/anime_mal_id.json')); + } + + /** + * @return array + * @throws FileNotFoundException + */ + private function loadFailedMalIds() : array + { + if (!Storage::exists('indexer/indexer_anime.failed')) { + throw new FileNotFoundException('"indexer/indexer_anime.failed" does not exist'); + } + + return json_decode(Storage::get('indexer/indexer_anime.failed')); + } + +} diff --git a/app/Console/Commands/Indexer/AnimeScheduleIndexer.php b/app/Console/Commands/Indexer/AnimeScheduleIndexer.php new file mode 100644 index 0000000..4a5b4a0 --- /dev/null +++ b/app/Console/Commands/Indexer/AnimeScheduleIndexer.php @@ -0,0 +1,92 @@ +serialize( + app('JikanParser') + ->getSchedule(new ScheduleRequest()), + 'json' + ), + true + ); + + if (HttpHelper::hasError($results)) { + echo "FAILED: {$results->original['error']}\n"; + return; + } + + $anime = []; + + foreach ($results as $day) { + foreach ($day as $entry) { + $anime[] = $entry; + } + } + + $i = 1; + $itemCount = count($anime); + echo "Anime currently airing: {$itemCount} entries\n"; + foreach ($anime as $entry) { + $url = env('APP_URL') . "/v4/anime/{$entry['mal_id']}"; + + file_get_contents($url); + sleep(3); // prevent rate-limit + + echo "Updating {$i}/{$itemCount} \r"; + try { + } catch (\Exception $e) { + echo "[SKIPPED] Failed to fetch {$url}"; + } + $i++; + } + + echo str_pad("Indexing complete", 10).PHP_EOL; + } +} diff --git a/app/Console/Commands/Indexer/CommonIndexer.php b/app/Console/Commands/Indexer/CommonIndexer.php new file mode 100644 index 0000000..25924b6 --- /dev/null +++ b/app/Console/Commands/Indexer/CommonIndexer.php @@ -0,0 +1,188 @@ +serialize( + app('JikanParser') + ->getProducers(new ProducersRequest()), + 'json' + ), + true + )['producers']; + + if (HttpHelper::hasError($results)) { + echo "FAILED: {$results->original['error']}\n"; + return; + } + + $itemCount = count($results); + echo "Parsed {$itemCount} producers\n"; + foreach ($results as $i => $item) { + $result = DB::table('producers') + ->where('mal_id', $item['mal_id']) + ->updateOrInsert(['request_hash'=>'request:producers:'.sha1($item['mal_id'].$item['name'])]+$item); + echo "Indexing {$i}/{$itemCount} \r"; + } + + /** + * Magazines + */ + echo "Indexing Magazines...\n"; + $results = \json_decode( + app('SerializerV4')->serialize( + app('JikanParser') + ->getMagazines(new MagazinesRequest()), + 'json' + ), + true + )['magazines']; + + if (HttpHelper::hasError($results)) { + echo "FAILED: {$results->original['error']}\n"; + return; + } + + $itemCount = count($results); + echo "Parsed {$itemCount} magazines\n"; + foreach ($results as $i => $item) { + $result = DB::table('magazines') + ->where('mal_id', $item['mal_id']) + ->updateOrInsert(['request_hash'=>'request:magazines:'.sha1($item['mal_id'].$item['name'])]+$item); + echo "Indexing {$i}/{$itemCount} \r"; + } + + /** + * Anime Genres + */ + echo "Indexing Anime Genres...\n"; + $results = \json_decode( + app('SerializerV4')->serialize( + app('JikanParser') + ->getAnimeGenres(new AnimeGenresRequest()), + 'json' + ), + true + ); + + if (HttpHelper::hasError($results)) { + echo "FAILED: {$results->original['error']}\n"; + return; + } + + $itemCount = count($results['genres']); + echo "Parsed {$itemCount} anime genres\n"; + foreach ($results['genres'] as $i => $item) { + $result = DB::table('genres_anime') + ->where('mal_id', $item['mal_id']) + ->updateOrInsert(['request_hash'=>'request:anime_genres:'.sha1($item['mal_id'].$item['name'])]+$item); + echo "Indexing {$i}/{$itemCount} \r"; + } + + $itemCount = count($results['explicit_genres']); + echo "Parsed {$itemCount} anime explicit_genres\n"; + foreach ($results['explicit_genres'] as $i => $item) { + $result = DB::table('explicit_genres_anime') + ->where('mal_id', $item['mal_id']) + ->updateOrInsert(['request_hash'=>'request:anime_explicit_genres:'.sha1($item['mal_id'].$item['name'])]+$item); + echo "Indexing {$i}/{$itemCount} \r"; + } + + $itemCount = count($results['themes']); + echo "Parsed {$itemCount} anime themes\n"; + foreach ($results['themes'] as $i => $item) { + $result = DB::table('themes_anime') + ->where('mal_id', $item['mal_id']) + ->updateOrInsert(['request_hash'=>'request:anime_themes:'.sha1($item['mal_id'].$item['name'])]+$item); + echo "Indexing {$i}/{$itemCount} \r"; + } + + $itemCount = count($results['demographics']); + echo "Parsed {$itemCount} anime demographics\n"; + foreach ($results['demographics'] as $i => $item) { + $result = DB::table('demographics_anime') + ->where('mal_id', $item['mal_id']) + ->updateOrInsert(['request_hash'=>'request:anime_demographics:'.sha1($item['mal_id'].$item['name'])]+$item); + echo "Indexing {$i}/{$itemCount} \r"; + } + + /** + * Manga Genres + */ + echo "Indexing Manga Genres...\n"; + $results = \json_decode( + app('SerializerV4')->serialize( + app('JikanParser') + ->getMangaGenres(new MangaGenresRequest()), + 'json' + ), + true + )['genres']; + + if (HttpHelper::hasError($results)) { + echo "FAILED: {$results->original['error']}\n"; + return; + } + + $itemCount = count($results); + echo "Parsed {$itemCount} manga genres\n"; + foreach ($results as $i => $item) { + $result = DB::table('genres_manga') + ->where('mal_id', $item['mal_id']) + ->updateOrInsert(['request_hash'=>'request:manga_genres:'.sha1($item['mal_id'].$item['name'])]+$item); + echo "Indexing {$i}/{$itemCount} \r"; + } + + echo str_pad("Indexing complete", 10).PHP_EOL; + } +} diff --git a/app/Console/Commands/Indexer/CurrentSeasonIndexer.php b/app/Console/Commands/Indexer/CurrentSeasonIndexer.php new file mode 100644 index 0000000..4700475 --- /dev/null +++ b/app/Console/Commands/Indexer/CurrentSeasonIndexer.php @@ -0,0 +1,84 @@ +serialize( + app('JikanParser') + ->getSeasonal(new SeasonalRequest()), + 'json' + ), + true + ); + + if (HttpHelper::hasError($results)) { + echo "FAILED: {$results->original['error']}\n"; + return; + } + + $anime = $results['anime']; + $itemCount = count($anime); + echo "Anime in current season: {$itemCount} entries\n"; + foreach ($anime as $i => $entry) { + $url = env('APP_URL') . "/v4/anime/{$entry['mal_id']}"; + + file_get_contents($url); + sleep(3); // prevent rate-limit + + echo "Updating {$i}/{$itemCount} {$url} [{$entry['mal_id']} - {$entry['title']}] \n"; + try { + } catch (\Exception $e) { + echo "[SKIPPED] Failed to fetch {$url}\n"; + } + } + + echo str_pad("Indexing complete", 100).PHP_EOL; + } +} diff --git a/app/Console/Commands/Indexer/GenreIndexer.php b/app/Console/Commands/Indexer/GenreIndexer.php new file mode 100644 index 0000000..608313b --- /dev/null +++ b/app/Console/Commands/Indexer/GenreIndexer.php @@ -0,0 +1,240 @@ +serialize( + app('JikanParser') + ->getAnimeGenres(new AnimeGenresRequest()), + 'json' + ), + true + ); + + if (HttpHelper::hasError($results)) { + echo "FAILED: {$results->original['error']}\n"; + return; + } + + $itemCount = count($results['genres']); + echo "Parsed {$itemCount} anime genres\n"; + foreach ($results['genres'] as $i => $item) { + $item['count'] = $item['count']; + + $result = DB::table('genres_anime') +// ->where('mal_id', $item['mal_id']) + ->updateOrInsert( + [ + 'mal_id' => $item['mal_id'] + ], + [ + 'mal_id' => $item['mal_id'], + 'name' => $item['name'], + 'url' => $item['url'], + 'count' => $item['count'] + ] + ); + echo "Indexing {$i}/{$itemCount} \r"; + } + + $itemCount = count($results['explicit_genres']); + echo "Parsed {$itemCount} anime explicit_genres\n"; + foreach ($results['explicit_genres'] as $i => $item) { + $result = DB::table('explicit_genres_anime') +// ->where('mal_id', $item['mal_id']) + ->updateOrInsert( + [ + 'mal_id' => $item['mal_id'] + ], + [ + 'mal_id' => $item['mal_id'], + 'name' => $item['name'], + 'url' => $item['url'], + 'count' => $item['count'] + ] + ); + echo "Indexing {$i}/{$itemCount} \r"; + } + + $itemCount = count($results['themes']); + echo "Parsed {$itemCount} anime themes\n"; + foreach ($results['themes'] as $i => $item) { + $result = DB::table('themes_anime') +// ->where('mal_id', $item['mal_id']) + ->updateOrInsert( + [ + 'mal_id' => $item['mal_id'] + ], + [ + 'mal_id' => $item['mal_id'], + 'name' => $item['name'], + 'url' => $item['url'], + 'count' => $item['count'] + ] + ); + echo "Indexing {$i}/{$itemCount} \r"; + } + + $itemCount = count($results['demographics']); + echo "Parsed {$itemCount} anime demographics\n"; + foreach ($results['demographics'] as $i => $item) { + $result = DB::table('demographics_anime') +// ->where('mal_id', $item['mal_id']) + ->updateOrInsert( + [ + 'mal_id' => $item['mal_id'] + ], + [ + 'mal_id' => $item['mal_id'], + 'name' => $item['name'], + 'url' => $item['url'], + 'count' => $item['count'] + ] + ); + echo "Indexing {$i}/{$itemCount} \r"; + } + + /** + * Manga Genres + */ + echo "Indexing Manga Genres...\n"; + $results = \json_decode( + app('SerializerV4')->serialize( + app('JikanParser') + ->getMangaGenres(new MangaGenresRequest()), + 'json' + ), + true + ); + + if (HttpHelper::hasError($results)) { + echo "FAILED: {$results->original['error']}\n"; + return; + } + + $itemCount = count($results['genres']); + echo "Parsed {$itemCount} manga genres\n"; + foreach ($results['genres'] as $i => $item) { + $result = DB::table('genres_manga') +// ->where('mal_id', $item['mal_id']) + ->updateOrInsert( + [ + 'mal_id' => $item['mal_id'] + ], + [ + 'mal_id' => $item['mal_id'], + 'name' => $item['name'], + 'url' => $item['url'], + 'count' => $item['count'] + ] + ); + echo "Indexing {$i}/{$itemCount} \r"; + } + + $itemCount = count($results['explicit_genres']); + echo "Parsed {$itemCount} manga explicit_genres\n"; + foreach ($results['explicit_genres'] as $i => $item) { + $result = DB::table('explicit_genres_manga') +// ->where('mal_id', $item['mal_id']) + ->updateOrInsert( + [ + 'mal_id' => $item['mal_id'] + ], + [ + 'mal_id' => $item['mal_id'], + 'name' => $item['name'], + 'url' => $item['url'], + 'count' => $item['count'] + ] + ); + echo "Indexing {$i}/{$itemCount} \r"; + } + + $itemCount = count($results['themes']); + echo "Parsed {$itemCount} manga themes\n"; + foreach ($results['themes'] as $i => $item) { + $result = DB::table('themes_manga') +// ->where('mal_id', $item['mal_id']) + ->updateOrInsert( + [ + 'mal_id' => $item['mal_id'] + ], + [ + 'mal_id' => $item['mal_id'], + 'name' => $item['name'], + 'url' => $item['url'], + 'count' => $item['count'] + ] + ); + echo "Indexing {$i}/{$itemCount} \r"; + } + + $itemCount = count($results['demographics']); + echo "Parsed {$itemCount} manga demographics\n"; + foreach ($results['demographics'] as $i => $item) { + $result = DB::table('demographics_manga') +// ->where('mal_id', $item['mal_id']) + ->updateOrInsert( + [ + 'mal_id' => $item['mal_id'] + ], + [ + 'mal_id' => $item['mal_id'], + 'name' => $item['name'], + 'url' => $item['url'], + 'count' => $item['count'] + ] + ); + echo "Indexing {$i}/{$itemCount} \r"; + } + + + echo str_pad("Indexing complete", 10).PHP_EOL; + } +} diff --git a/app/Console/Commands/Indexer/MangaIndexer.php b/app/Console/Commands/Indexer/MangaIndexer.php new file mode 100644 index 0000000..abeefba --- /dev/null +++ b/app/Console/Commands/Indexer/MangaIndexer.php @@ -0,0 +1,172 @@ +option('failed') ?? false; + $resume = $this->option('resume') ?? false; + $reverse = $this->option('reverse') ?? false; + $delay = $this->option('delay') ?? 3; + $index = $this->option('index') ?? 0; + + $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"); + + if ($failed && Storage::exists('indexer/indexer_manga.save')) { + $this->ids = $this->loadFailedMalIds(); + } + + if (!$failed) { + $this->ids = $this->fetchMalIds(); + } + + // start from the end + if ($reverse) { + $this->ids = array_reverse($this->ids); + } + + // Resume + if ($resume && Storage::exists('indexer/indexer_manga.save')) { + $index = (int)Storage::get('indexer/indexer_manga.save'); + + $this->info("Resuming from index: {$index}"); + } + + // check if index even exists + if ($index > 0 && !isset($this->ids[$index])) { + $index = 0; + $this->warn('Invalid index; set back to 0'); + } + + // initialize and index + Storage::put('indexer/indexer_manga.save', 0); + + echo "Loading MAL IDs\n"; + $count = count($this->ids); + $failedIds = []; + $success = []; + + echo "{$count} entries available\n"; + for ($i = $index; $i <= ($count - 1); $i++) { + $id = $this->ids[$i]; + + $url = env('APP_URL') . "/v4/manga/{$id}"; + + echo "Indexing/Updating " . ($i + 1) . "/{$count} {$url} [MAL ID: {$id}] \n"; + + try { + $response = json_decode(file_get_contents($url), true); + + if (isset($response['error']) && $response['status'] != 404) { + echo "[SKIPPED] Failed to fetch {$url} - {$response['error']}\n"; + $failedIds[] = $id; + Storage::put('indexer/indexer_manga.failed', json_encode($failedIds)); + } + + sleep($delay); + } catch (\Exception $e) { + echo "[SKIPPED] Failed to fetch {$url}\n"; + $failedIds[] = $id; + Storage::put('indexer/indexer_manga.failed', json_encode($failedIds)); + } + + $success[] = $id; + Storage::put('indexer/indexer_manga.save', $i); + } + + Storage::delete('indexer/indexer_manga.save'); + + echo "---------\nIndexing complete\n"; + echo count($success) . " entries indexed or updated\n"; + echo count($failedIds) . " entries failed to index or update. Re-run with --failed to requeue failed entries only\n"; + } + + /** + * @return array + * @url https://github.com/seanbreckenridge/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"); + + $ids = json_decode( + file_get_contents('https://raw.githubusercontent.com/seanbreckenridge/mal-id-cache/master/cache/manga_cache.json'), + true + ); + + $this->ids = $ids['sfw'] + $ids['nsfw']; // merge + Storage::put('indexer/manga_mal_id.json', json_encode($this->ids)); + + return json_decode(Storage::get('indexer/manga_mal_id.json')); + } + + /** + * @return array + * @throws FileNotFoundException + */ + private function loadFailedMalIds() : array + { + if (!Storage::exists('indexer/indexer_manga.failed')) { + throw new FileNotFoundException('"indexer/indexer_manga.failed" does not exist'); + } + + return json_decode(Storage::get('indexer/indexer_manga.failed')); + } + +} diff --git a/app/Console/Commands/ManageMicrocaching.php b/app/Console/Commands/ManageMicrocaching.php new file mode 100644 index 0000000..b418f33 --- /dev/null +++ b/app/Console/Commands/ManageMicrocaching.php @@ -0,0 +1,69 @@ +argument('status'), ['disable', 'enable'])) { + $this->error('Only [enable/disable] allowed'); + return; + } + + if (!env('CACHING') || env('CACHE_DRIVER') !== 'redis') { + $this->error('Could not enable MICROCACHING. CACHING must be set to true and CACHE_DRIVER must be redis'); + } + + $enabled = $this->argument('status') === 'enable'; + + if ($enabled === env('MICROCACHING')) { + $this->error("MICROCACHING is already '{$this->argument('status')}'"); + return; + } + + $path = base_path('.env'); + + if (!file_exists($path)) { + $this->error(".env does not exist"); + return; + } + + file_put_contents($path, str_replace( + 'MICROCACHING='.(env('MICROCACHING') ? 'true' : 'false'), 'MICROCACHING='.($enabled ? 'true' : 'false'), file_get_contents($path) + )); + + $this->info("MICROCACHING: '{$this->argument('status')}'"); + } +} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 50b37f8..f5c201e 100755 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -4,6 +4,13 @@ namespace App\Console; use App\Console\Commands\ClearQueuedJobs; use App\Console\Commands\CacheRemove; +use App\Console\Commands\Indexer\AnimeIndexer; +use App\Console\Commands\Indexer\AnimeScheduleIndexer; +use App\Console\Commands\Indexer\CommonIndexer; +use App\Console\Commands\Indexer\CurrentSeasonIndexer; +use App\Console\Commands\Indexer\GenreIndexer; +use App\Console\Commands\Indexer\MangaIndexer; +use App\Console\Commands\ManageMicrocaching; use App\Console\Commands\ModifyCacheDriver; use App\Console\Commands\ModifyCacheMethod; use Illuminate\Console\Scheduling\Schedule; @@ -21,6 +28,13 @@ class Kernel extends ConsoleKernel ModifyCacheDriver::class, ClearQueuedJobs::class, CacheRemove::class, + CommonIndexer::class, + AnimeScheduleIndexer::class, + CurrentSeasonIndexer::class, + ManageMicrocaching::class, + AnimeIndexer::class, + MangaIndexer::class, + GenreIndexer::class ]; /** @@ -31,6 +45,20 @@ class Kernel extends ConsoleKernel */ protected function schedule(Schedule $schedule) { - // + // Update Scheduled Anime and current season data daily + // since they're airing, they're more prone to + // have their information updated + $schedule->command('indexer:anime-schedule') + ->daily(); + + $schedule->command('indexer:anime-current-season') + ->daily(); + + // Update common indexes daily + $schedule->command('indexer:common') + ->daily(); + + $schedule->command('indexer:genres') + ->daily(); } } 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 @@ +health = $health ?? self::BAD_HEALTH; + $this->status = $status ?? 0; + } +} diff --git a/app/Exceptions/Console/CommandAlreadyRunningException.php b/app/Exceptions/Console/CommandAlreadyRunningException.php new file mode 100644 index 0000000..5ca1767 --- /dev/null +++ b/app/Exceptions/Console/CommandAlreadyRunningException.php @@ -0,0 +1,8 @@ +name = \get_class($exception); @@ -82,10 +82,12 @@ class GithubReport $report->jikanVersion = Versions::getVersion('jikan-me/jikan'); $report->phpVersion = PHP_VERSION; - try { - $report->redisRunning = trim(app('redis')->ping()) === 'PONG' ? "Connected" : "Disconnected"; - } catch (ConnectionException $e) { - $report->redisRunning = false; + if (env('CACHING') && env('CACHE_DRIVER') === 'redis') { + try { + $report->redisRunning = trim(app('redis')->ping()) === 'PONG' ? "Connected" : "Disconnected"; + } catch (ConnectionException $e) { + $report->redisRunning = false; + } } $report->instanceType = 'UNKNOWN'; diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 5a14238..beb9bf2 100755 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -2,10 +2,11 @@ namespace App\Exceptions; +use App\Events\SourceHeartbeatEvent; use App\Http\HttpHelper; -use Bugsnag\BugsnagLaravel\Facades\Bugsnag; use Exception; use GuzzleHttp\Exception\ClientException; +use GuzzleHttp\Exception\ConnectException; use Illuminate\Auth\Access\AuthorizationException; use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Http\Request; @@ -15,6 +16,7 @@ use Jikan\Exception\ParserException; use Laravel\Lumen\Exceptions\Handler as ExceptionHandler; use Predis\Connection\ConnectionException; use Symfony\Component\Debug\Exception\FlattenException; +use Symfony\Component\HttpClient\Exception\TimeoutException; use Symfony\Component\HttpKernel\Exception\HttpException; use Illuminate\Support\Facades\Cache; @@ -38,25 +40,27 @@ class Handler extends ExceptionHandler ]; /** - * @param Exception $e + * @param \Throwable $e * @throws Exception */ - public function report(Exception $e) + public function report(\Throwable $e) { parent::report($e); } /** - * Render an exception into an HTTP response. - * - * @param \Illuminate\Http\Request $request - * @param \Exception $e - * @return \Illuminate\Http\Response + * @param Request $request + * @param \Throwable $e + * @return \Illuminate\Http\JsonResponse|\Symfony\Component\HttpFoundation\Response */ - public function render($request, Exception $e) + public function render($request, \Throwable $e) { $githubReport = GithubReport::make($e, $request); + if (app()->bound('sentry') && $this->shouldReport($e)) { + app('sentry')->captureException($e); + } + // ConnectionException from Redis server if ($e instanceof ConnectionException) { /* @@ -77,6 +81,18 @@ class Handler extends ExceptionHandler ], 500); } + if ($e instanceof ConnectException) { + event(new SourceHeartbeatEvent(SourceHeartbeatEvent::BAD_HEALTH, $e->getCode())); + + return response() + ->json([ + 'status' => $e->getCode(), + 'type' => 'BadResponseException', + 'message' => 'Jikan failed to connect to MyAnimeList. MyAnimeList may be down/unavailable or refuses to connect', + 'error' => $e->getMessage() + ], 503); + } + // ParserException from Jikan PHP API if ($e instanceof ParserException) { $githubReport->setRepo(env('GITHUB_API', 'jikan-me/jikan')); @@ -95,7 +111,7 @@ class Handler extends ExceptionHandler if ($e instanceof BadResponseException || $e instanceof ClientException) { switch ($e->getCode()) { case 404: - $this->set404Cache($request, $e); +// $this->set404Cache($request, $e); return response() ->json([ @@ -112,15 +128,20 @@ class Handler extends ExceptionHandler 'message' => 'Jikan is being rate limited by MyAnimeList', 'error' => $e->getMessage() ], $e->getCode()); + case 403: case 500: + case 501: case 502: case 503: case 504: + // Dispatch Bad source health event to prompt database fallback if enabled + event(new SourceHeartbeatEvent(SourceHeartbeatEvent::BAD_HEALTH, $e->getCode())); + return response() ->json([ 'status' => $e->getCode(), 'type' => 'BadResponseException', - 'message' => 'Jikan could not connect to MyAnimeList', + 'message' => 'Jikan failed to connect to MyAnimeList. MyAnimeList may be down/unavailable or refuses to connect', 'error' => $e->getMessage() ], 503); default: @@ -145,6 +166,16 @@ class Handler extends ExceptionHandler ], 400); } + if ($e instanceof TimeoutException) { + return response() + ->json([ + 'status' => 408, + 'type' => 'TimeoutException', + 'message' => 'Request to MyAnimeList.net timed out (' .env('SOURCE_TIMEOUT', 5) . ' seconds)', + 'error' => $e->getMessage() + ], 408); + } + if ($e instanceof Exception) { return response() ->json([ @@ -158,8 +189,16 @@ class Handler extends ExceptionHandler } } + /** + * @param Request $request + * @param BadResponseException $e + */ private function set404Cache(Request $request, BadResponseException $e) { + if (!env('CACHING') || env('MICROCACHING')) { + return; + } + $fingerprint = "request:404:".sha1(env('APP_URL') . $request->getRequestUri()); if (Cache::has($fingerprint)) { diff --git a/app/GenreAnime.php b/app/GenreAnime.php new file mode 100644 index 0000000..380066b --- /dev/null +++ b/app/GenreAnime.php @@ -0,0 +1,54 @@ +getAnimeGenres(new AnimeGenresRequest()); + + return json_decode( + app('SerializerV4') + ->serialize($data, 'json'), + true + ); + } +} \ No newline at end of file diff --git a/app/GenreManga.php b/app/GenreManga.php new file mode 100644 index 0000000..6aca334 --- /dev/null +++ b/app/GenreManga.php @@ -0,0 +1,53 @@ +getAnimeGenres(new AnimeGenresRequest()); + + return json_decode( + app('SerializerV4') + ->serialize($data, 'json'), + true + ); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/V3/ClubController.php b/app/Http/Controllers/V3/ClubController.php deleted file mode 100755 index 1c3d12b..0000000 --- a/app/Http/Controllers/V3/ClubController.php +++ /dev/null @@ -1,21 +0,0 @@ -jikan->getClub(new ClubRequest($id)); - return response($this->serializer->serialize($club, 'json')); - } - - public function members(int $id, int $page = 1) - { - $club = ['members' => $this->jikan->getClubUsers(new UserListRequest($id, $page))]; - return response($this->serializer->serialize($club, 'json')); - } -} diff --git a/app/Http/Controllers/V3/ExampleController.php b/app/Http/Controllers/V3/ExampleController.php deleted file mode 100755 index aab066e..0000000 --- a/app/Http/Controllers/V3/ExampleController.php +++ /dev/null @@ -1,18 +0,0 @@ -jikan->getMagazine(new MagazineRequest($id, $page)); - return response($this->serializer->serialize($magazine, 'json')); - } -} diff --git a/app/Http/Controllers/V3/PersonController.php b/app/Http/Controllers/V3/PersonController.php deleted file mode 100755 index 9ab9826..0000000 --- a/app/Http/Controllers/V3/PersonController.php +++ /dev/null @@ -1,30 +0,0 @@ -jikan->getPerson(new PersonRequest($id)); - return response($this->serializer->serialize($person, 'json')); - } - - public function pictures(int $id) - { - if ($id < 1) { // MAL INCONSISTENCY: doesn't return 404, it returns an error message with HTTP 200 instead - throw new BadResponseException(null, 404); - } - - $person = ['pictures' => $this->jikan->getPersonPictures(new PersonPicturesRequest($id))]; - return response($this->serializer->serialize($person, 'json')); - } -} diff --git a/app/Http/Controllers/V3/ProducerController.php b/app/Http/Controllers/V3/ProducerController.php deleted file mode 100755 index 5aba89e..0000000 --- a/app/Http/Controllers/V3/ProducerController.php +++ /dev/null @@ -1,14 +0,0 @@ -jikan->getProducer(new ProducerRequest($id, $page)); - return response($this->serializer->serialize($producer, 'json')); - } -} diff --git a/app/Http/Controllers/V3/ScheduleController.php b/app/Http/Controllers/V3/ScheduleController.php deleted file mode 100755 index 3b8dc7d..0000000 --- a/app/Http/Controllers/V3/ScheduleController.php +++ /dev/null @@ -1,39 +0,0 @@ -json([ - 'error' => 'Bad Request', - ])->setStatusCode(400); - } - - $schedule = $this->jikan->getSchedule(new ScheduleRequest()); - - if (null !== $day) { - $schedule = [ - strtolower($day) => $schedule->{'get'.ucfirst(strtolower($day))}(), - ]; - } - - return response($this->serializer->serialize($schedule, 'json')); - } -} diff --git a/app/Http/Controllers/V3/SeasonController.php b/app/Http/Controllers/V3/SeasonController.php deleted file mode 100755 index 8078811..0000000 --- a/app/Http/Controllers/V3/SeasonController.php +++ /dev/null @@ -1,45 +0,0 @@ -json([ - 'error' => 'Bad Request' - ])->setStatusCode(400); - } - - $season = $this->jikan->getSeasonal(new SeasonalRequest($year, $season)); - - return response($this->serializer->serialize($season, 'json')); - } - - public function archive() - { - return response( - $this->serializer->serialize( - ['archive' => $this->jikan->getSeasonList(new SeasonListRequest())], - 'json' - ) - ); - } - - public function later() - { - $season = $this->jikan->getSeasonal(new SeasonalRequest(null, null, true)); - return response($this->serializer->serialize($season, 'json')); - } -} diff --git a/app/Http/Controllers/V3/TopController.php b/app/Http/Controllers/V3/TopController.php deleted file mode 100755 index c5b63b3..0000000 --- a/app/Http/Controllers/V3/TopController.php +++ /dev/null @@ -1,75 +0,0 @@ -json([ - 'error' => 'Bad Request' - ])->setStatusCode(400); - } - - $anime = $this->jikan->getTopAnime(new TopAnimeRequest($page, $type)); - - $top = ['top' => $this->jikan->getTopAnime(new TopAnimeRequest($page, $type))]; - - return response($this->serializer->serialize($top, 'json')); - } - - public function manga(int $page = 1, string $type = null) - { - if (!is_null($type) && !\in_array( - strtolower($type), - [ - JikanConstants::TOP_MANGA, - JikanConstants::TOP_NOVEL, - JikanConstants::TOP_ONE_SHOT, - JikanConstants::TOP_DOUJINSHI, - JikanConstants::TOP_MANHWA, - JikanConstants::TOP_MANHUA, - JikanConstants::TOP_BY_POPULARITY, - JikanConstants::TOP_BY_FAVORITES, - ] - )) { - return response()->json([ - 'error' => 'Bad Request' - ])->setStatusCode(400); - } - - $top = ['top' => $this->jikan->getTopManga(new TopMangaRequest($page, $type))]; - - return response($this->serializer->serialize($top, 'json')); - } - - public function people(int $page = 1) - { - $top = ['top' => $this->jikan->getTopPeople(new TopPeopleRequest($page))]; - - return response($this->serializer->serialize($top, 'json')); - } - - public function characters(int $page = 1) - { - $top = ['top' => $this->jikan->getTopCharacters(new TopCharactersRequest($page))]; - - return response($this->serializer->serialize($top, 'json')); - } -} diff --git a/app/Http/Controllers/V3/AnimeController.php b/app/Http/Controllers/V4/AnimeController.php similarity index 77% rename from app/Http/Controllers/V3/AnimeController.php rename to app/Http/Controllers/V4/AnimeController.php index 64e0e4d..6568b0b 100755 --- a/app/Http/Controllers/V3/AnimeController.php +++ b/app/Http/Controllers/V4/AnimeController.php @@ -1,9 +1,13 @@ jikan->getAnime(new AnimeRequest($id)); - return response($this->serializer->serialize($anime, 'json')); + + $animeSerialized = $this->serializer->serialize($anime, 'json'); + $animeSerialized = HttpHelper::serializeEmptyObjectsControllerLevel( + json_decode($animeSerialized, true) + ); + $animeSerialized = json_encode($animeSerialized); + + return response($animeSerialized); } public function characters_staff(int $id) @@ -30,8 +42,15 @@ class AnimeController extends Controller return response($this->serializer->serialize($anime, 'json')); } - public function episodes(int $id, int $page = 1) + public function episode(int $id, int $episodeId) { + $anime = $this->jikan->getAnimeEpisode(new AnimeEpisodeRequest($id, $episodeId)); + return response($this->serializer->serialize($anime, 'json')); + } + + public function episodes(int $id) + { + $page = $_GET['page'] ?? 1; $anime = $this->jikan->getAnimeEpisodes(new AnimeEpisodesRequest($id, $page)); return response($this->serializer->serialize($anime, 'json')); } @@ -42,13 +61,9 @@ class AnimeController extends Controller return response($this->serializer->serialize($anime, 'json')); } - public function forum(int $id, ?string $topic = null) + public function forum(int $id) { - if ($topic === 'episodes') { - $topic = 'episode'; - } - - $anime = ['topics' => $this->jikan->getAnimeForum(new AnimeForumRequest($id, $topic))]; + $anime = ['topics' => $this->jikan->getAnimeForum(new AnimeForumRequest($id))]; return response($this->serializer->serialize($anime, 'json')); } diff --git a/app/Http/Controllers/V3/CharacterController.php b/app/Http/Controllers/V4/CharacterController.php similarity index 81% rename from app/Http/Controllers/V3/CharacterController.php rename to app/Http/Controllers/V4/CharacterController.php index 9b9c42a..744bd12 100755 --- a/app/Http/Controllers/V3/CharacterController.php +++ b/app/Http/Controllers/V4/CharacterController.php @@ -1,9 +1,13 @@ serializer = SerializerFactory::createV3(); - $this->jikan = app('JikanParser'); + $this->serializer = SerializerFactory::createV4(); + $this->jikan = $jikan; } } diff --git a/app/Http/Controllers/V3/GenreController.php b/app/Http/Controllers/V4/GenreController.php similarity index 54% rename from app/Http/Controllers/V3/GenreController.php rename to app/Http/Controllers/V4/GenreController.php index a130732..415adef 100755 --- a/app/Http/Controllers/V3/GenreController.php +++ b/app/Http/Controllers/V4/GenreController.php @@ -1,8 +1,9 @@ jikan->getMangaGenre(new MangaGenreRequest($id, $page)); return response($this->serializer->serialize($person, 'json')); } + + public function animeListing() + { + $results = $this->jikan->getAnimeGenres(new AnimeGenresRequest()); + return response($this->serializer->serialize($results, 'json')); + } + + public function mangaListing() + { + $results = $this->jikan->getAnimeGenres(new AnimeGenresRequest()); + return response($this->serializer->serialize($results, 'json')); + } } diff --git a/app/Http/Controllers/V4/MagazineController.php b/app/Http/Controllers/V4/MagazineController.php new file mode 100755 index 0000000..d282ce1 --- /dev/null +++ b/app/Http/Controllers/V4/MagazineController.php @@ -0,0 +1,22 @@ +jikan->getMagazines(new MagazinesRequest()); + return response($this->serializer->serialize($results, 'json')); + } + + public function resource(int $id, int $page = 1) + { + $magazine = $this->jikan->getMagazine(new MagazineRequest($id, $page)); + return response($this->serializer->serialize($magazine, 'json')); + } +} diff --git a/app/Http/Controllers/V3/MangaController.php b/app/Http/Controllers/V4/MangaController.php similarity index 73% rename from app/Http/Controllers/V3/MangaController.php rename to app/Http/Controllers/V4/MangaController.php index 4a0b409..a1aafdc 100755 --- a/app/Http/Controllers/V3/MangaController.php +++ b/app/Http/Controllers/V4/MangaController.php @@ -1,7 +1,8 @@ jikan->getManga(new MangaRequest($id)); - return response($this->serializer->serialize($manga, 'json')); + + $mangaSerialized = $this->serializer->serialize($manga, 'json'); + $mangaSerialized = HttpHelper::serializeEmptyObjectsControllerLevel( + json_decode($mangaSerialized, true) + ); + + return response($this->serializer->serialize($mangaSerialized, 'json')); } public function characters(int $id) @@ -33,14 +40,9 @@ class MangaController extends Controller return response($this->serializer->serialize($manga, 'json')); } - public function forum(int $id, ?string $topic = null) + public function forum(int $id) { - // safely bypass MAL's naming schemes - if ($topic === 'chapters') { - $topic = 'episode'; - } - - $manga = ['topics' => $this->jikan->getMangaForum(new MangaForumRequest($id, $topic))]; + $manga = ['topics' => $this->jikan->getMangaForum(new MangaForumRequest($id))]; return response($this->serializer->serialize($manga, 'json')); } @@ -59,24 +61,24 @@ class MangaController extends Controller public function moreInfo(int $id) { $manga = ['moreinfo' => $this->jikan->getMangaMoreInfo(new MangaMoreInfoRequest($id))]; - return response(json_encode($manga)); + return response($this->serializer->serialize($manga, 'json')); } public function recommendations(int $id) { - $anime = ['recommendations' => $this->jikan->getMangaRecommendations(new MangaRecommendationsRequest($id))]; - return response($this->serializer->serialize($anime, 'json')); + $manga = ['recommendations' => $this->jikan->getMangaRecommendations(new MangaRecommendationsRequest($id))]; + return response($this->serializer->serialize($manga, 'json')); } public function userupdates(int $id, int $page = 1) { - $anime = ['users' => $this->jikan->getMangaRecentlyUpdatedByUsers(new MangaRecentlyUpdatedByUsersRequest($id, $page))]; - return response($this->serializer->serialize($anime, 'json')); + $manga = ['users' => $this->jikan->getMangaRecentlyUpdatedByUsers(new MangaRecentlyUpdatedByUsersRequest($id, $page))]; + return response($this->serializer->serialize($manga, 'json')); } public function reviews(int $id, int $page = 1) { - $anime = ['reviews' => $this->jikan->getMangaReviews(new MangaReviewsRequest($id, $page))]; - return response($this->serializer->serialize($anime, 'json')); + $manga = ['reviews' => $this->jikan->getMangaReviews(new MangaReviewsRequest($id, $page))]; + return response($this->serializer->serialize($manga, 'json')); } } diff --git a/app/Http/Controllers/V4/ProducerController.php b/app/Http/Controllers/V4/ProducerController.php new file mode 100755 index 0000000..d44e3ba --- /dev/null +++ b/app/Http/Controllers/V4/ProducerController.php @@ -0,0 +1,22 @@ +jikan->getProducers(new ProducersRequest()); + return response($this->serializer->serialize($results, 'json')); + } + + public function resource(int $id, int $page = 1) + { + $producer = $this->jikan->getProducer(new ProducerRequest($id, $page)); + return response($this->serializer->serialize($producer, 'json')); + } +} diff --git a/app/Http/Controllers/V4/RecommendationsController.php b/app/Http/Controllers/V4/RecommendationsController.php new file mode 100644 index 0000000..ba1dec0 --- /dev/null +++ b/app/Http/Controllers/V4/RecommendationsController.php @@ -0,0 +1,34 @@ + $this->jikan->getRecentRecommendations( + new RecentRecommendationsRequest(Constants::RECENT_RECOMMENDATION_ANIME, $page) + ) + ]; + + return response($this->serializer->serialize($results, 'json')); + } + + public function manga() + { + $page = $_GET['page'] ?? 1; + $results = [ + 'recommendations' => $this->jikan->getRecentRecommendations( + new RecentRecommendationsRequest(Constants::RECENT_RECOMMENDATION_MANGA, $page) + ) + ]; + + return response($this->serializer->serialize($results, 'json')); + } +} diff --git a/app/Http/Controllers/V4/ReviewsController.php b/app/Http/Controllers/V4/ReviewsController.php new file mode 100644 index 0000000..6554564 --- /dev/null +++ b/app/Http/Controllers/V4/ReviewsController.php @@ -0,0 +1,40 @@ +jikan->getRecentReviews( + new RecentReviewsRequest(Constants::RECENT_REVIEW_BEST_VOTED, $page) + ); + + return response($this->serializer->serialize($results, 'json')); + } + + public function anime() + { + $page = $_GET['page'] ?? 1; + $results = $this->jikan->getRecentReviews( + new RecentReviewsRequest(Constants::RECENT_REVIEW_ANIME, $page) + ); + + return response($this->serializer->serialize($results, 'json')); + } + + public function manga() + { + $page = $_GET['page'] ?? 1; + $results = $this->jikan->getRecentReviews( + new RecentReviewsRequest(Constants::RECENT_REVIEW_MANGA, $page) + ); + + return response($this->serializer->serialize($results, 'json')); + } +} diff --git a/app/Http/Controllers/V3/SearchController.php b/app/Http/Controllers/V4/SearchController.php similarity index 70% rename from app/Http/Controllers/V3/SearchController.php rename to app/Http/Controllers/V4/SearchController.php index b48db5f..ec31f0a 100755 --- a/app/Http/Controllers/V3/SearchController.php +++ b/app/Http/Controllers/V4/SearchController.php @@ -1,8 +1,7 @@ jikan->getAnimeSearch( SearchQueryBuilder::create( - $request, (new AnimeSearchRequest())->setPage($page) ) ); @@ -28,22 +28,20 @@ class SearchController extends Controller return response($this->filter($search)); } - public function manga(Request $request, int $page = 1) + public function manga(int $page = 1) { $search = $this->jikan->getMangaSearch( SearchQueryBuilder::create( - $request, (new MangaSearchRequest())->setPage($page) ) ); return response($this->filter($search)); } - public function people(Request $request, int $page = 1) + public function people(int $page = 1) { $search = $this->jikan->getPersonSearch( SearchQueryBuilder::create( - $request, (new PersonSearchRequest())->setPage($page) ) ); @@ -51,11 +49,10 @@ class SearchController extends Controller return response($this->filter($search)); } - public function character(Request $request, int $page = 1) + public function character(int $page = 1) { $search = $this->jikan->getCharacterSearch( SearchQueryBuilder::create( - $request, (new CharacterSearchRequest())->setPage($page) ) ); @@ -63,6 +60,26 @@ class SearchController extends Controller return response($this->filter($search)); } + public function users() + { + $search = $this->jikan->getUserSearch( + SearchQueryBuilder::create( + new UserSearchRequest() + ) + ); + + return response($this->filter($search)); + } + + public function userById(int $id) + { + $search = $this->jikan->getUsernameById( + new UsernameByIdRequest($id) + ); + + return response($this->filter($search)); + } + private function filter($object) { diff --git a/app/Http/Controllers/V3/UserController.php b/app/Http/Controllers/V4/UserController.php similarity index 61% rename from app/Http/Controllers/V3/UserController.php rename to app/Http/Controllers/V4/UserController.php index a4ace35..bd37a59 100755 --- a/app/Http/Controllers/V3/UserController.php +++ b/app/Http/Controllers/V4/UserController.php @@ -1,21 +1,23 @@ jikan->getUserProfile(new UserProfileRequest($username)); - return response($this->serializer->serialize($person, 'json')); + $user = $this->jikan->getUserProfile(new UserProfileRequest($username)); + return response($this->serializer->serialize($user, 'json')); } public function history(string $username, ?string $type = null) @@ -37,7 +39,7 @@ class UserController extends Controller return response($this->serializer->serialize($person, 'json')); } - public function animelist(Request $request, string $username, ?string $status = null, int $page = 1) + public function animelist(string $username, ?string $status = null, int $page = 1) { if (!is_null($status)) { $status = strtolower($status); @@ -54,12 +56,7 @@ class UserController extends Controller $this->serializer->serialize( [ 'anime' => $this->jikan->getUserAnimeList( - UserListQueryBuilder::create( - $request, - (new UserAnimeListRequest($username)) - ->setPage($page) - ->setStatus($status) - ) + new UserAnimeListRequest($username, $page, $status) ) ], 'json' @@ -67,7 +64,7 @@ class UserController extends Controller ); } - public function mangalist(Request $request, string $username, ?string $status = null, int $page = 1) + public function mangalist(string $username, ?string $status = null, int $page = 1) { if (!is_null($status)) { $status = strtolower($status); @@ -84,12 +81,7 @@ class UserController extends Controller $this->serializer->serialize( [ 'manga' => $this->jikan->getUserMangaList( - UserListQueryBuilder::create( - $request, - (new UserMangaListRequest($username)) - ->setPage($page) - ->setStatus($status) - ) + new UserMangaListRequest($username, $page, $status) ) ], 'json' @@ -97,6 +89,47 @@ class UserController extends Controller ); } + public function reviews(string $username) + { + $page = $_GET['page'] ?? 1; + $results = $this->jikan->getUserReviews( + new UserReviewsRequest($username, $page) + ); + + return response($this->serializer->serialize($results, 'json')); + } + + public function recommendations(string $username) + { + $page = $_GET['page'] ?? 1; + $results = $this->jikan->getUserRecommendations( + new UserRecommendationsRequest($username, $page) + ); + + return response($this->serializer->serialize($results, 'json')); + } + + public function clubs(string $username) + { + $results = [ + 'clubs' => $this->jikan->getUserClubs( + new UserClubsRequest($username) + ) + ]; + + return response($this->serializer->serialize($results, 'json')); + } + + public function recentlyOnline() + { + $results = [ + 'users' => $this->jikan->getRecentOnlineUsers( + new RecentlyOnlineUsersRequest() + ) + ]; + + return response($this->serializer->serialize($results, 'json')); + } private function listStatusToId(?string $status) : int { diff --git a/app/Http/Controllers/V4/WatchController.php b/app/Http/Controllers/V4/WatchController.php new file mode 100644 index 0000000..d86df3b --- /dev/null +++ b/app/Http/Controllers/V4/WatchController.php @@ -0,0 +1,51 @@ +jikan->getRecentEpisodes( + new RecentEpisodesRequest() + ); + + return response($this->serializer->serialize($results, 'json')); + } + + public function popularEpisodes() + { + $results = $this->jikan->getPopularEpisodes( + new PopularEpisodesRequest() + ); + + return response($this->serializer->serialize($results, 'json')); + } + + public function recentPromos() + { + $page = $_GET['page'] ?? 1; + $results = $this->jikan->getRecentPromotionalVideos( + new RecentPromotionalVideosRequest($page) + ); + + return response($this->serializer->serialize($results, 'json')); + } + + public function popularPromos() + { + $results = $this->jikan->getPopularPromotionalVideos( + new PopularPromotionalVideosRequest() + ); + + return response($this->serializer->serialize($results, 'json')); + } + +} diff --git a/app/Http/Controllers/V4DB/AnimeController.php b/app/Http/Controllers/V4DB/AnimeController.php new file mode 100644 index 0000000..e10a83a --- /dev/null +++ b/app/Http/Controllers/V4DB/AnimeController.php @@ -0,0 +1,1088 @@ +where('mal_id', $id) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $response = Anime::scrape($id); + + 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()) { + Anime::query() + ->insert($response); + } + + if ($this->isExpired($request, $results)) { + Anime::query() + ->where('mal_id', $id) + ->update($response); + } + + $results = Anime::query() + ->where('mal_id', $id) + ->get(); + } + + if ($results->isEmpty()) { + return HttpResponse::notFound($request); + } + + $response = (new \App\Http\Resources\V4\AnimeResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + /** + * @OA\Get( + * path="/anime/{id}/characters", + * operationId="getAnimeCharacters", + * tags={"anime"}, + * + * @OA\Parameter( + * name="id", + * in="path", + * required=true, + * @OA\Schema(type="integer") + * ), + * + * @OA\Response( + * response="200", + * description="Returns anime characters resource", + * @OA\JsonContent( + * ref="#/components/schemas/anime characters" + * ) + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ) + */ + public function characters(Request $request, int $id) + { + $results = DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $anime = $this->jikan->getAnimeCharactersAndStaff(new AnimeCharactersAndStaffRequest($id)); + $response = \json_decode($this->serializer->serialize($anime, 'json'), true); + + $results = $this->updateCache($request, $results, $response); + } + + $response = (new AnimeCharactersResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + /** + * @OA\Get( + * path="/anime/{id}/staff", + * operationId="getAnimeStaff", + * tags={"anime"}, + * + * @OA\Parameter( + * name="id", + * in="path", + * required=true, + * @OA\Schema(type="integer") + * ), + * + * @OA\Response( + * response="200", + * description="Returns anime staff resource", + * @OA\JsonContent( + * ref="#/components/schemas/anime staff" + * ) + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ) + */ + public function staff(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 = $this->jikan->getAnimeCharactersAndStaff(new AnimeCharactersAndStaffRequest($id)); + $response = \json_decode($this->serializer->serialize($anime, 'json'), true); + + $results = $this->updateCache($request, $results, $response); + } + + $response = (new AnimeStaffResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + /** + * @OA\Get( + * path="/anime/{id}/episodes", + * operationId="getAnimeEpisodes", + * tags={"anime"}, + * + * @OA\Parameter( + * name="id", + * in="path", + * required=true, + * @OA\Schema(type="integer") + * ), + * + * @OA\Parameter(ref="#/components/parameters/page"), + * + * @OA\Response( + * response="200", + * description="Returns a list of anime episodes", + * @OA\JsonContent( + * ref="#/components/schemas/anime episodes" + * ) + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ) + * + * @OA\Schema( + * schema="anime episodes", + * description="Anime Episodes Resource", + * + * allOf={ + * @OA\Schema(ref="#/components/schemas/pagination"), + * @OA\Schema( + * @OA\Property( + * property="data", + * type="array", + * @OA\Items( + * type="object", + * @OA\Property( + * property="mal_id", + * type="integer", + * description="MyAnimeList ID" + * ), + * @OA\Property( + * property="url", + * type="string", + * description="MyAnimeList URL" + * ), + * @OA\Property( + * property="title", + * type="string", + * description="Title" + * ), + * @OA\Property( + * property="title_japanese", + * type="string", + * description="Title Japanese" + * ), + * @OA\Property( + * property="title_romanji", + * type="string", + * description="title_romanji" + * ), + * @OA\Property( + * property="duration", + * type="integer", + * description="Episode duration in seconds" + * ), + * @OA\Property( + * property="aired", + * type="string", + * description="Aired Date ISO8601" + * ), + * @OA\Property( + * property="filler", + * type="boolean", + * description="Filler episode" + * ), + * @OA\Property( + * property="recap", + * type="boolean", + * description="Recap episode" + * ), + * @OA\Property( + * property="forum_url", + * type="string", + * description="Episode discussion forum URL" + * ), + * ), + * ), + * ), + * } + * ) + */ + public function episodes(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 = $this->jikan->getAnimeEpisodes(new AnimeEpisodesRequest($id, $page)); + $response = \json_decode($this->serializer->serialize($anime, 'json'), true); + + $results = $this->updateCache($request, $results, $response); + } + + $response = (new ResultsResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + /** + * @OA\Get( + * path="/anime/{id}/episodes/{episode}", + * operationId="getAnimeEpisodeById", + * tags={"anime"}, + * + * @OA\Parameter( + * name="id", + * in="path", + * required=true, + * @OA\Schema(type="integer") + * ), + * + * @OA\Parameter( + * name="episode", + * in="path", + * required=true, + * @OA\Schema(type="integer") + * ), + * + * @OA\Response( + * response="200", + * description="Returns a single anime episode resource", + * @OA\JsonContent( + * ref="#/components/schemas/anime episode" + * ) + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ) + */ + public function episode(Request $request, int $id, int $episodeId) + { + $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); + + $results = $this->updateCache($request, $results, $response); + } + + $response = (new AnimeEpisodeResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + /** + * @OA\Get( + * path="/anime/{id}/news", + * operationId="getAnimeNews", + * tags={"anime"}, + * + * @OA\Parameter( + * name="id", + * in="path", + * required=true, + * @OA\Schema(type="integer") + * ), + * + * + * @OA\Parameter(ref="#/components/parameters/page"), + * + * @OA\Response( + * response="200", + * description="Returns a list of news articles related to the entry", + * @OA\JsonContent( + * ref="#/components/schemas/anime news" + * ) + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ) + * + * @OA\Schema( + * schema="anime news", + * description="Anime News Resource", + * + * allOf={ + * @OA\Schema(ref="#/components/schemas/pagination"), + * @OA\Schema( + * ref="#/components/schemas/news", + * ), + * } + * ) + */ + public function news(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 = $this->jikan->getNewsList(new AnimeNewsRequest($id, $page)); + $response = \json_decode($this->serializer->serialize($anime, 'json'), true); + + $results = $this->updateCache($request, $results, $response); + } + + $response = (new ResultsResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + /** + * @OA\Get( + * path="/anime/{id}/forum", + * operationId="getAnimeForum", + * tags={"anime"}, + * + * @OA\Parameter( + * name="id", + * in="path", + * required=true, + * @OA\Schema(type="integer") + * ), + * + * @OA\Parameter( + * name="topic", + * in="query", + * required=false, + * description="Filter topics", + * @OA\Schema(type="string",enum={"all", "episode", "other"}) + * ), + * + * @OA\Response( + * response="200", + * description="Returns a list of forum topics related to the entry", + * @OA\JsonContent( + * ref="#/components/schemas/forum" + * ) + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ) + */ + public function forum(Request $request, int $id) + { + $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); + + $results = $this->updateCache($request, $results, $response); + } + + $response = (new ForumResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + /** + * @OA\Get( + * path="/anime/{id}/videos", + * operationId="getAnimeVideos", + * tags={"anime"}, + * + * @OA\Parameter( + * name="id", + * in="path", + * required=true, + * @OA\Schema(type="integer") + * ), + * + * @OA\Response( + * response="200", + * description="Returns videos related to the entry", + * @OA\JsonContent( + * ref="#/components/schemas/anime videos" + * ) + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ) + */ + public function videos(Request $request, int $id) + { + $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); + + $results = $this->updateCache($request, $results, $response); + } + + $response = (new AnimeVideosResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + /** + * @OA\Get( + * path="/anime/{id}/pictures", + * operationId="getAnimePictures", + * tags={"anime"}, + * + * @OA\Parameter( + * name="id", + * in="path", + * required=true, + * @OA\Schema(type="integer") + * ), + * + * @OA\Response( + * response="200", + * description="Returns pictures related to the entry", + * @OA\JsonContent( + * ref="#/components/schemas/pictures variants" + * ) + * ), + * + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ) + * + */ + public function pictures(Request $request, int $id) + { + $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); + + $results = $this->updateCache($request, $results, $response); + } + + $response = (new PicturesResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + /** + * @OA\Get( + * path="/anime/{id}/statistics", + * operationId="getAnimeStatistics", + * tags={"anime"}, + * + * @OA\Parameter( + * name="id", + * in="path", + * required=true, + * @OA\Schema(type="integer") + * ), + * + * @OA\Response( + * response="200", + * description="Returns anime statistics", + * @OA\JsonContent( + * ref="#/components/schemas/anime statistics" + * ) + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ) + */ + public function stats(Request $request, int $id) + { + $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); + + $results = $this->updateCache($request, $results, $response); + } + + $response = (new AnimeStatisticsResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + /** + * @OA\Get( + * path="/anime/{id}/moreinfo", + * operationId="getAnimeMoreInfo", + * tags={"anime"}, + * + * @OA\Parameter( + * name="id", + * in="path", + * required=true, + * @OA\Schema(type="integer") + * ), + * + * @OA\Response( + * response="200", + * description="Returns anime statistics", + * @OA\JsonContent( + * ref="#/components/schemas/moreinfo" + * ) + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ) + */ + public function moreInfo(Request $request, int $id) + { + $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); + + $results = $this->updateCache($request, $results, $response); + } + + $response = (new MoreInfoResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + /** + * @OA\Get( + * path="/anime/{id}/recommendations", + * operationId="getAnimeRecommendations", + * tags={"anime"}, + * + * @OA\Parameter( + * name="id", + * in="path", + * required=true, + * @OA\Schema(type="integer") + * ), + * + * @OA\Response( + * response="200", + * description="Returns anime recommendations", + * @OA\JsonContent( + * ref="#/components/schemas/entry recommendations" + * ) + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ) + */ + public function recommendations(Request $request, int $id) + { + $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); + + $results = $this->updateCache($request, $results, $response); + } + + $response = (new RecommendationsResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + /** + * @OA\Get( + * path="/anime/{id}/userupdates", + * operationId="getAnimeUserUpdates", + * tags={"anime"}, + * + * @OA\Parameter(ref="#/components/parameters/page"), + * + * @OA\Parameter( + * name="id", + * in="path", + * required=true, + * @OA\Schema(type="integer") + * ), + * + * @OA\Response( + * response="200", + * description="Returns a list of users who have added/updated/removed the entry on their list", + * @OA\JsonContent( + * ref="#/components/schemas/anime userupdates" + * ) + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ) + */ + public function userupdates(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 = $this->jikan->getAnimeRecentlyUpdatedByUsers(new AnimeRecentlyUpdatedByUsersRequest($id, $page)); + $response = \json_decode($this->serializer->serialize($anime, 'json'), true); + + $results = $this->updateCache($request, $results, $response); + } + + $response = (new ResultsResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + /** + * @OA\Get( + * path="/anime/{id}/reviews", + * operationId="getAnimeReviews", + * tags={"anime"}, + * + * @OA\Parameter( + * name="id", + * in="path", + * required=true, + * @OA\Schema(type="integer") + * ), + * + * @OA\Parameter(ref="#/components/parameters/page"), + * + * @OA\Response( + * response="200", + * description="Returns anime reviews", + * @OA\JsonContent( + * ref="#/components/schemas/anime reviews" + * ) + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ) + */ + 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 = $this->jikan->getAnimeReviews(new AnimeReviewsRequest($id, $page)); + $response = \json_decode($this->serializer->serialize($anime, 'json'), true); + + $results = $this->updateCache($request, $results, $response); + } + + $response = (new ResultsResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + + /** + * @OA\Get( + * path="/anime/{id}/relations", + * operationId="getAnimeRelations", + * tags={"anime"}, + * + * + * @OA\Response( + * response="200", + * description="Returns anime relations", + * @OA\JsonContent( + * ref="#/components/schemas/relation" + * ) + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ) + */ + public function relations(Request $request, int $id) + { + $results = Anime::query() + ->where('mal_id', $id) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $response = Anime::scrape($id); + + if ($results->isEmpty()) { + $meta = [ + 'createdAt' => new UTCDateTime(), + 'modifiedAt' => new UTCDateTime(), + 'request_hash' => $this->fingerprint + ]; + } + $meta['modifiedAt'] = new UTCDateTime(); + + $response = $meta + $response; + + if ($results->isEmpty()) { + Anime::query() + ->insert($response); + } + + if ($this->isExpired($request, $results)) { + Anime::query() + ->where('mal_id', $id) + ->update($response); + } + + $results = Anime::query() + ->where('mal_id', $id) + ->get(); + } + + if ($results->isEmpty()) { + return HttpResponse::notFound($request); + } + + $response = (new AnimeRelationsResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + /** + * @OA\Get( + * path="/anime/{id}/themes", + * operationId="getAnimeThemes", + * tags={"anime"}, + * + * @OA\Response( + * response="200", + * description="Returns anime themes", + * @OA\JsonContent( + * ref="#/components/schemas/anime themes" + * ) + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ) + */ + public function themes(Request $request, int $id) + { + $results = Anime::query() + ->where('mal_id', $id) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $response = Anime::scrape($id); + + if ($results->isEmpty()) { + $meta = [ + 'createdAt' => new UTCDateTime(), + 'modifiedAt' => new UTCDateTime(), + 'request_hash' => $this->fingerprint + ]; + } + $meta['modifiedAt'] = new UTCDateTime(); + + $response = $meta + $response; + + if ($results->isEmpty()) { + Anime::query() + ->insert($response); + } + + if ($this->isExpired($request, $results)) { + Anime::query() + ->where('mal_id', $id) + ->update($response); + } + + $results = Anime::query() + ->where('mal_id', $id) + ->get(); + } + + if ($results->isEmpty()) { + return HttpResponse::notFound($request); + } + + + $response = (new AnimeThemesResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } +} diff --git a/app/Http/Controllers/V4DB/CharacterController.php b/app/Http/Controllers/V4DB/CharacterController.php new file mode 100644 index 0000000..67241bc --- /dev/null +++ b/app/Http/Controllers/V4DB/CharacterController.php @@ -0,0 +1,400 @@ +where('mal_id', $id) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $response = Character::scrape($id); + + 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()) { + Character::query() + ->insert($response); + } + + if ($this->isExpired($request, $results)) { + Character::query() + ->where('mal_id', $id) + ->update($response); + } + + $results = Character::query() + ->where('mal_id', $id) + ->get(); + } + + if ($results->isEmpty()) { + return HttpResponse::notFound($request); + } + + $response = (new \App\Http\Resources\V4\CharacterResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + /** + * @OA\Get( + * path="/characters/{id}/anime", + * operationId="getCharacterAnime", + * tags={"characters"}, + * + * @OA\Response( + * response="200", + * description="Returns anime that character is in", + * @OA\JsonContent(ref="#/components/schemas/character anime") + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ) + */ + public function anime(Request $request, int $id) + { + $results = Character::query() + ->where('mal_id', $id) + ->get(); + + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $response = Character::scrape($id); + + if ($results->isEmpty()) { + $meta = [ + 'createdAt' => new UTCDateTime(), + 'modifiedAt' => new UTCDateTime(), + 'request_hash' => $this->fingerprint + ]; + } + $meta['modifiedAt'] = new UTCDateTime(); + + $response = $meta + $response; + + if ($results->isEmpty()) { + Character::query() + ->insert($response); + } + + if ($this->isExpired($request, $results)) { + Character::query() + ->where('mal_id', $id) + ->update($response); + } + + $results = Character::query() + ->where('mal_id', $id) + ->get(); + } + + if ($results->isEmpty()) { + return HttpResponse::notFound($request); + } + + $response = (new CharacterAnimeCollection( + $results->first()['animeography'] + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + + /** + * @OA\Get( + * path="/characters/{id}/manga", + * operationId="getCharacterManga", + * tags={"characters"}, + * + * @OA\Response( + * response="200", + * description="Returns manga that character is in", + * @OA\JsonContent(ref="#/components/schemas/character manga") + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ) + */ + public function manga(Request $request, int $id) + { + $results = Character::query() + ->where('mal_id', $id) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $response = Character::scrape($id); + + if ($results->isEmpty()) { + $meta = [ + 'createdAt' => new UTCDateTime(), + 'modifiedAt' => new UTCDateTime(), + 'request_hash' => $this->fingerprint + ]; + } + $meta['modifiedAt'] = new UTCDateTime(); + + $response = $meta + $response; + + if ($results->isEmpty()) { + Character::query() + ->insert($response); + } + + if ($this->isExpired($request, $results)) { + Character::query() + ->where('mal_id', $id) + ->update($response); + } + + $results = Character::query() + ->where('mal_id', $id) + ->get(); + } + + if ($results->isEmpty()) { + return HttpResponse::notFound($request); + } + + $response = (new CharacterMangaCollection( + $results->first()['mangaography'] + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + /** + * @OA\Get( + * path="/characters/{id}/voices", + * operationId="getCharacterVoiceActors", + * tags={"characters"}, + * + * @OA\Response( + * response="200", + * description="Returns the character's voice actors", + * @OA\JsonContent(ref="#/components/schemas/character voice actors") + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ) + */ + public function voices(Request $request, int $id) + { + $results = Character::query() + ->where('mal_id', $id) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $response = Character::scrape($id); + + if ($results->isEmpty()) { + $meta = [ + 'createdAt' => new UTCDateTime(), + 'modifiedAt' => new UTCDateTime(), + 'request_hash' => $this->fingerprint + ]; + } + $meta['modifiedAt'] = new UTCDateTime(); + + $response = $meta + $response; + + if ($results->isEmpty()) { + Character::query() + ->insert($response); + } + + if ($this->isExpired($request, $results)) { + Character::query() + ->where('mal_id', $id) + ->update($response); + } + + $results = Character::query() + ->where('mal_id', $id) + ->get(); + } + + if ($results->isEmpty()) { + return HttpResponse::notFound($request); + } + + $response = (new CharacterSeiyuuCollection( + $results->first()['voice_actors'] + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + /** + * @OA\Get( + * path="/characters/{id}/pictures", + * operationId="getCharacterPictures", + * tags={"characters"}, + * + * @OA\Parameter( + * name="id", + * in="path", + * required=true, + * @OA\Schema(type="integer") + * ), + * + * @OA\Response( + * response="200", + * description="Returns pictures related to the entry", + * @OA\JsonContent( + * ref="#/components/schemas/pictures" + * ) + * ), + * + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ) + * + * @OA\Schema( + * schema="character pictures", + * description="Character Pictures", + * @OA\Property( + * property="data", + * type="array", + * + * @OA\Items( + * @OA\Property( + * property="image_url", + * type="string", + * description="Default JPG Image Size URL" + * ), + * @OA\Property( + * property="large_image_url", + * type="string", + * description="Large JPG Image Size URL" + * ), + * ) + * ) + * ) + */ + public function pictures(Request $request, int $id) + { + $results = DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $character = ['pictures' => $this->jikan->getCharacterPictures(new CharacterPicturesRequest($id))]; + $response = \json_decode($this->serializer->serialize($character, 'json'), true); + + $results = $this->updateCache($request, $results, $response); + } + + $response = (new PicturesResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } +} diff --git a/app/Http/Controllers/V4DB/ClubController.php b/app/Http/Controllers/V4DB/ClubController.php new file mode 100644 index 0000000..34d178d --- /dev/null +++ b/app/Http/Controllers/V4DB/ClubController.php @@ -0,0 +1,363 @@ +where('mal_id', $id) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $response = Club::scrape($id); + + 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()) { + Club::query() + ->insert($response); + } + + if ($this->isExpired($request, $results)) { + Club::query() + ->where('mal_id', $id) + ->update($response); + } + + $results = Club::query() + ->where('mal_id', $id) + ->get(); + } + + + if ($results->isEmpty()) { + return HttpResponse::notFound($request); + } + + $response = (new \App\Http\Resources\V4\ClubResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + /** + * @OA\Get( + * path="/clubs/{id}/members", + * operationId="getClubMembers", + * tags={"clubs"}, + * + * @OA\Parameter( + * name="id", + * in="path", + * required=true, + * @OA\Schema(type="integer") + * ), + * + * @OA\Parameter(ref="#/components/parameters/page"), + * + * @OA\Response( + * response="200", + * description="Returns Club Members Resource", + * @OA\JsonContent( + * allOf={ + * @OA\Schema(ref="#/components/schemas/pagination"), + * @OA\Schema( + * ref="#/components/schemas/club member" + * ) + * } + * ) + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ), + * + * @OA\Schema( + * schema="club member", + * description="Club Member", + * @OA\Property( + * property="data", + * type="array", + * @OA\Items( + * type="object", + * @OA\Property( + * property="username", + * type="string", + * description="MyAnimeList Username" + * ), + * @OA\Property( + * property="url", + * type="string", + * description="MyAnimeList URL" + * ), + * @OA\Property( + * property="image_url", + * type="string", + * description="MyAnimeList Image URL" + * ), + * ), + * ), + * ), + */ + public function members(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 = ['results' => $this->jikan->getClubUsers(new UserListRequest($id, $page))]; + $response = \json_decode($this->serializer->serialize($anime, 'json'), true); + + $results = $this->updateCache($request, $results, $response); + } + + $response = (new ResultsResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + /** + * @OA\Get( + * path="/clubs/{id}/staff", + * operationId="getClubStaff", + * tags={"clubs"}, + * + * @OA\Parameter( + * name="id", + * in="path", + * required=true, + * @OA\Schema(type="integer") + * ), + * + * @OA\Response( + * response="200", + * description="Returns Club Staff", + * @OA\JsonContent( + * ref="#/components/schemas/club staff" + * ) + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ) + */ + public function staff(Request $request, int $id) + { + $results = Club::query() + ->where('mal_id', $id) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $response = Club::scrape($id); + + 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()) { + Club::query() + ->insert($response); + } + + if ($this->isExpired($request, $results)) { + Club::query() + ->where('mal_id', $id) + ->update($response); + } + + $results = Club::query() + ->where('mal_id', $id) + ->get(); + } + + + if ($results->isEmpty()) { + return HttpResponse::notFound($request); + } + + $response = (new \App\Http\Resources\V4\ClubStaffResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + /** + * @OA\Get( + * path="/clubs/{id}/relations", + * operationId="getClubRelations", + * tags={"clubs"}, + * + * @OA\Parameter( + * name="id", + * in="path", + * required=true, + * @OA\Schema(type="integer") + * ), + * + * @OA\Response( + * response="200", + * description="Returns Club Relations", + * @OA\JsonContent( + * ref="#/components/schemas/club relations" + * ) + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ) + */ + public function relations(Request $request, int $id) + { + $results = Club::query() + ->where('mal_id', $id) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $response = Club::scrape($id); + + 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()) { + Club::query() + ->insert($response); + } + + if ($this->isExpired($request, $results)) { + Club::query() + ->where('mal_id', $id) + ->update($response); + } + + $results = Club::query() + ->where('mal_id', $id) + ->get(); + } + + + if ($results->isEmpty()) { + return HttpResponse::notFound($request); + } + + $response = (new \App\Http\Resources\V4\ClubRelationsResource( + $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 new file mode 100644 index 0000000..cc6f287 --- /dev/null +++ b/app/Http/Controllers/V4DB/Controller.php @@ -0,0 +1,246 @@ +serializer = SerializerFactory::createV4(); + $this->jikan = $jikan; + $this->fingerprint = HttpHelper::resolveRequestFingerprint($request); + } + + protected function isExpired($request, $results) : bool + { + $lastModified = $this->getLastModified($results); + + if ($lastModified === null) { + return true; + } + + $routeName = HttpHelper::getRouteName($request); + $expiry = (int) config("controller.{$routeName}.ttl") + $lastModified; + + if (time() > $expiry) { + return true; + } + + 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'); + } + + return null; + } + + 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) + ) + ); + } + + protected function updateCache($request, $results, $response) + { + // If resource doesn't exist, prepare meta + if ($results->isEmpty()) { + $meta = [ + 'createdAt' => new UTCDateTime(), + 'modifiedAt' => new UTCDateTime(), + 'request_hash' => $this->fingerprint + ]; + } + + // Update `modifiedAt` meta + $meta['modifiedAt'] = new UTCDateTime(); + // join meta data with response + $response = $meta + $response; + + // insert cache if resource doesn't exist + if ($results->isEmpty()) { + DB::table($this->getRouteTable($request)) + ->insert($response); + } + + // update cache if resource exists + if ($this->isExpired($request, $results)) { + DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->update($response); + } + + // return results + return DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->get(); + } + + /** + * @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; +// } + + /** + * @param Request $request + * @param array $response + * @return array + */ + private function mutation() : void + { + $requestType = HttpHelper::requestType($this->request); + + if (($requestType === 'anime' || $requestType === 'manga')) { + + // Fix JSON response for empty related object + if (isset($this->response['related']) && \count($this->response['related']) === 0) { + $this->response['related'] = new \stdClass(); + } + + if (isset($this->response['related']) && !is_object($this->response['related']) && !empty($this->response['related'])) { + $relation = []; + foreach ($this->response['related'] as $relationType => $related) { + $relation[] = [ + 'relation' => $relationType, + 'entry' => $related + ]; + } + $this->response['related'] = $relation; + } + } + } + +} diff --git a/app/Http/Controllers/V4DB/GenreController.php b/app/Http/Controllers/V4DB/GenreController.php new file mode 100644 index 0000000..32fbb0a --- /dev/null +++ b/app/Http/Controllers/V4DB/GenreController.php @@ -0,0 +1,148 @@ +get('filter') ?? null; + + $explicitGenres = DB::table('explicit_genres_anime')->get(); + $themes = DB::table('themes_anime')->get(); + $demographics = DB::table('demographics_anime')->get(); + + switch ($filter) { + case 'genres': + $results = GenreAnime::query() + ->get(); + break; + case 'explicit_genres': + $results = $explicitGenres; + break; + case 'themes': + $results = $themes; + break; + case 'demographics': + $results = $demographics; + break; + default: + $results = GenreAnime::query() + ->get() + ->concat($explicitGenres->all()) + ->concat($themes->all()) + ->concat($demographics->all()); + break; + } + + return new GenreCollection( + $results + ); + } + + /** + * @OA\Get( + * path="/genres/manga", + * operationId="getMangaGenres", + * tags={"genres"}, + * + * @OA\Parameter(ref="#/components/parameters/page"), + * @OA\Parameter(ref="#/components/parameters/limit"), + * + * @OA\Parameter( + * name="filter", + * in="query", + * @OA\Schema(ref="#/components/schemas/genre query filter") + * ), + + * @OA\Response( + * response="200", + * description="Returns entry genres, explicit_genres, themes and demographics", + * @OA\JsonContent( + * ref="#/components/schemas/genres" + * ) + * ), + * + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ) + */ + public function manga(Request $request): GenreCollection + { + $filter = $request->get('filter') ?? null; + + $explicitGenres = DB::table('explicit_genres_manga')->get(); + $themes = DB::table('themes_manga')->get(); + $demographics = DB::table('demographics_manga')->get(); + + switch ($filter) { + case 'genres': + $results = GenreManga::query() + ->get(); + break; + case 'explicit_genres': + $results = $explicitGenres; + break; + case 'themes': + $results = $themes; + break; + case 'demographics': + $results = $demographics; + break; + default: + $results = GenreManga::query() + ->get() + ->concat($explicitGenres->all()) + ->concat($themes->all()) + ->concat($demographics->all()); + break; + } + + return new GenreCollection( + $results + ); + } +} diff --git a/app/Http/Controllers/V4DB/InsightsController.php b/app/Http/Controllers/V4DB/InsightsController.php new file mode 100644 index 0000000..97bfa4b --- /dev/null +++ b/app/Http/Controllers/V4DB/InsightsController.php @@ -0,0 +1,155 @@ +json([ + 'status' => 403, + 'type' => 'InsightsRuntimeException', + 'message' => 'Insights service is disabled', + 'error' => null + ], 403); + } + + $maxResultsPerPage = (int) env('MAX_RESULTS_PER_PAGE', 25); + + $page = $request->get('page') ?? 1; + $limit = $request->get('limit') ?? $maxResultsPerPage; + + $limit = (int) $limit; + + if ($limit <= 0) { + $limit = 1; + } + + if ($limit > $maxResultsPerPage) { + $limit = $maxResultsPerPage; + } + + $results = DB::table('insights') + ->where('timestamp', '>', time() - env('INSIGHTS_MAX_STORE_TIME', 172800) ) + ->orderBy('timestamp', 'desc') + ->paginate( + $limit, + ['*'], + null, + $page + ); + + return new InsightsCollection( + $results + ); + } + + const TRENDS = [ + 'anime', + 'manga', + 'people', + 'characters', + ]; + + public function trends(Request $request) + { + if (!env('INSIGHTS')) { + return response()->json([ + 'status' => 403, + 'type' => 'InsightsRuntimeException', + 'message' => 'Insights service is disabled', + 'error' => null + ], 403); + } + + $maxResultsPerPage = (int) env('MAX_RESULTS_PER_PAGE', 25); + + $page = $request->get('page') ?? 1; + $limit = $request->get('limit') ?? $maxResultsPerPage; + $trend = $request->get('trend') ?? null; + + $limit = (int) $limit; + + if ($limit <= 0) { + $limit = 1; + } + + if ($limit > $maxResultsPerPage) { + $limit = $maxResultsPerPage; + } + + if (is_null($trend) || !in_array($trend, self::TRENDS)) { + return response()->json([ + 'status' => 400, + 'type' => 'BadRequestException', + 'message' => 'Trend value is invalid', + 'error' => null + ], 400); + } + +// $results = DB::table('insights') +// ->where('url', 'regexp',"/\/v(\d)\/{$trend}\/(\d+).*/i") +// ->where('timestamp', '>', time() - env('INSIGHTS_MAX_STORE_TIME', 172800) ) +// ->orderBy('timestamp', 'desc') +// ->paginate( +// $limit, +// ['*'], +// null, +// $page +// ); + + $results = DB::table('insights') +// ->where('url', 'regexp',"/\/v(\d)\/{$trend}\/(\d+).*/i") + ->where('timestamp', '>', time() - env('INSIGHTS_MAX_STORE_TIME', 172800) ) + ->orderBy('timestamp', 'desc') + ->raw(fn($collection) => $collection->aggregate([ + [ + '$group' => [ + '_id' => [ + 'url' => '$url', + 'timestamp' => '$timestamp' + ], + 'urlCount' => [ '$sum' => 1 ] + ] + ], + [ + '$group' => [ + '_id' => '$_id.url', + 'count' => [ '$sum' => '$urlCount' ] + ] + ], + [ + '$sort' => [ 'count' => -1 ] + ], + ['$skip' => ($page - 1) * $maxResultsPerPage], + ['$limit' => $maxResultsPerPage], + ])); +// ->raw(fn($collection) => $collection->aggregate([ +// [ +// '$group' => [ +// '_id' => '$_id', +// 'url' => ['$first' => '$url'], +// 'count' => [ '$sum' => 1 ] +// ] +// ] +// ])); + + return new TrendsCollection( + new LengthAwarePaginator( + $results, DB::table('insights')->count(), $maxResultsPerPage, $page + ) + ); + } + + +} diff --git a/app/Http/Controllers/V4DB/MagazineController.php b/app/Http/Controllers/V4DB/MagazineController.php new file mode 100644 index 0000000..7e52c83 --- /dev/null +++ b/app/Http/Controllers/V4DB/MagazineController.php @@ -0,0 +1,92 @@ +get('page') ?? 1; + $limit = $request->get('limit') ?? self::MAX_RESULTS_PER_PAGE; + + if (!empty($limit)) { + $limit = (int) $limit; + + if ($limit <= 0) { + $limit = 1; + } + + if ($limit > self::MAX_RESULTS_PER_PAGE) { + $limit = self::MAX_RESULTS_PER_PAGE; + } + } + + $results = SearchQueryBuilderMagazine::query( + $request, + Magazine::query() + ); + + $results = $results + ->paginate( + $limit, + ['*'], + null, + $page + ); + + return new MagazineCollection( + $results + ); + } + + public function resource(Request $request, int $id) + { + $page = $request->get('page') ?? 1; + + $results = Manga::query() + ->where('serializations.mal_id', $id) + ->orderBy('title'); + + $results = $results + ->paginate( + self::MAX_RESULTS_PER_PAGE, + ['*'], + null, + $page + ); + + return new MangaCollection( + $results + ); + } +} diff --git a/app/Http/Controllers/V4DB/MangaController.php b/app/Http/Controllers/V4DB/MangaController.php new file mode 100644 index 0000000..6b21e6f --- /dev/null +++ b/app/Http/Controllers/V4DB/MangaController.php @@ -0,0 +1,658 @@ +where('mal_id', $id) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $response = Manga::scrape($id); + + 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()) { + Manga::query() + ->insert($response); + } + + if ($this->isExpired($request, $results)) { + Manga::query() + ->where('mal_id', $id) + ->update($response); + } + + $results = Manga::query() + ->where('mal_id', $id) + ->get(); + } + + + if ($results->isEmpty()) { + return HttpResponse::notFound($request); + } + + $response = (new \App\Http\Resources\V4\MangaResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + /** + * @OA\Get( + * path="/manga/{id}/characters", + * operationId="getMangaCharacters", + * tags={"manga"}, + * + * @OA\Response( + * response="200", + * description="Returns manga characters resource", + * @OA\JsonContent( + * ref="#/components/schemas/manga characters" + * ) + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ) + */ + public function characters(Request $request, int $id) + { + $results = DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $manga = ['characters' => $this->jikan->getMangaCharacters(new MangaCharactersRequest($id))]; + $response = \json_decode($this->serializer->serialize($manga, 'json'), true); + + $results = $this->updateCache($request, $results, $response); + } + + $response = (new MangaCharactersResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + /** + * @OA\Get( + * path="/manga/{id}/news", + * operationId="getMangaNews", + * tags={"manga"}, + * + * @OA\Response( + * response="200", + * description="Returns a list of manga news topics", + * @OA\JsonContent( + * ref="#/components/schemas/manga news" + * ) + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ) + * + * @OA\Schema( + * schema="manga news", + * description="Manga News Resource", + * + * allOf={ + * @OA\Schema(ref="#/components/schemas/pagination"), + * @OA\Schema(ref="#/components/schemas/news"), + * } + * ) + */ + public function news(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; + $manga = $this->jikan->getNewsList(new MangaNewsRequest($id, $page)); + $response = \json_decode($this->serializer->serialize($manga, 'json'), true); + + $results = $this->updateCache($request, $results, $response); + } + + $response = (new ResultsResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + /** + * @OA\Get( + * path="/manga/{id}/forum", + * operationId="getMangaTopics", + * tags={"manga"}, + * + * @OA\Response( + * response="200", + * description="Returns a list of manga forum topics", + * @OA\JsonContent( + * ref="#/components/schemas/forum" + * ) + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ) + */ + public function forum(Request $request, int $id) + { + $results = DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $topic = $request->get('topic'); + $manga = ['topics' => $this->jikan->getMangaForum(new MangaForumRequest($id))]; + $response = \json_decode($this->serializer->serialize($manga, 'json'), true); + + $results = $this->updateCache($request, $results, $response); + } + + $response = (new ForumResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + /** + * @OA\Get( + * path="/manga/{id}/pictures", + * operationId="getMangaPictures", + * tags={"manga"}, + * + * @OA\Response( + * response="200", + * description="Returns a list of manga forum topics", + * @OA\JsonContent( + * ref="#/components/schemas/pictures" + * ) + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ) + * @OA\Schema( + * schema="manga pictures", + * description="Manga Pictures", + * @OA\Property( + * property="data", + * type="array", + * + * @OA\Items( + * @OA\Property( + * property="image_url", + * type="string", + * description="Default JPG Image Size URL" + * ), + * @OA\Property( + * property="large_image_url", + * type="string", + * description="Large JPG Image Size URL" + * ), + * ) + * ) + * ) + */ + public function pictures(Request $request, int $id) + { + $results = DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $manga = ['pictures' => $this->jikan->getMangaPictures(new MangaPicturesRequest($id))]; + $response = \json_decode($this->serializer->serialize($manga, 'json'), true); + + $results = $this->updateCache($request, $results, $response); + } + + $response = (new PicturesResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + /** + * @OA\Get( + * path="/manga/{id}/statistics", + * operationId="getMangaStatistics", + * tags={"manga"}, + * + * @OA\Response( + * response="200", + * description="Returns anime statistics", + * @OA\JsonContent( + * ref="#/components/schemas/manga statistics" + * ) + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ) + */ + public function stats(Request $request, int $id) + { + $results = DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $manga = $this->jikan->getMangaStats(new MangaStatsRequest($id)); + $response = \json_decode($this->serializer->serialize($manga, 'json'), true); + + $results = $this->updateCache($request, $results, $response); + } + + $response = (new MangaStatisticsResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + /** + * @OA\Get( + * path="/manga/{id}/moreinfo", + * operationId="getMangaMoreInfo", + * tags={"manga"}, + * + * @OA\Response( + * response="200", + * description="Returns manga moreinfo", + * @OA\JsonContent( + * ref="#/components/schemas/moreinfo" + * ) + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ) + */ + public function moreInfo(Request $request, int $id) + { + $results = DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $manga = ['moreinfo' => $this->jikan->getMangaMoreInfo(new MangaMoreInfoRequest($id))]; + $response = \json_decode($this->serializer->serialize($manga, 'json'), true); + + $results = $this->updateCache($request, $results, $response); + } + + $response = (new MoreInfoResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + /** + * @OA\Get( + * path="/manga/{id}/recommendations", + * operationId="getMangaRecommendations", + * tags={"manga"}, + * + * @OA\Response( + * response="200", + * description="Returns manga recommendations", + * @OA\JsonContent( + * ref="#/components/schemas/entry recommendations" + * ) + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ) + */ + public function recommendations(Request $request, int $id) + { + $results = DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $manga = ['recommendations' => $this->jikan->getMangaRecommendations(new MangaRecommendationsRequest($id))]; + $response = \json_decode($this->serializer->serialize($manga, 'json'), true); + + $results = $this->updateCache($request, $results, $response); + } + + $response = (new RecommendationsResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + /** + * @OA\Get( + * path="/manga/{id}/userupdates", + * operationId="getMangaUserUpdates", + * tags={"manga"}, + * + * @OA\Response( + * response="200", + * description="Returns manga user updates", + * @OA\JsonContent( + * ref="#/components/schemas/manga userupdates" + * ) + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ) + */ + public function userupdates(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; + $manga = $this->jikan->getMangaRecentlyUpdatedByUsers(new MangaRecentlyUpdatedByUsersRequest($id, $page)); + $response = \json_decode($this->serializer->serialize($manga, 'json'), true); + + $results = $this->updateCache($request, $results, $response); + } + + $response = (new ResultsResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + /** + * @OA\Get( + * path="/manga/{id}/reviews", + * operationId="getMangaReviews", + * tags={"manga"}, + * + * @OA\Response( + * response="200", + * description="Returns manga reviews", + * @OA\JsonContent( + * ref="#/components/schemas/manga reviews" + * ) + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ) + */ + 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; + $manga = $this->jikan->getMangaReviews(new MangaReviewsRequest($id, $page)); + $response = \json_decode($this->serializer->serialize($manga, 'json'), true); + + $results = $this->updateCache($request, $results, $response); + } + + $response = (new ReviewsResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + /** + * @OA\Get( + * path="/manga/{id}/relations", + * operationId="getMangaRelations", + * tags={"manga"}, + * + * @OA\Parameter( + * name="id", + * in="path", + * required=true, + * @OA\Schema(type="integer") + * ), + * + * @OA\Response( + * response="200", + * description="Returns manga relations", + * @OA\JsonContent( + * ref="#/components/schemas/relation" + * ) + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ) + */ + public function relations(Request $request, int $id) + { + $results = Manga::query() + ->where('mal_id', $id) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $response = Manga::scrape($id); + + if ($results->isEmpty()) { + $meta = [ + 'createdAt' => new UTCDateTime(), + 'modifiedAt' => new UTCDateTime(), + 'request_hash' => $this->fingerprint + ]; + } + $meta['modifiedAt'] = new UTCDateTime(); + + $response = $meta + $response; + + if ($results->isEmpty()) { + Manga::query() + ->insert($response); + } + + if ($this->isExpired($request, $results)) { + Manga::query() + ->where('mal_id', $id) + ->update($response); + } + + $results = Manga::query() + ->where('mal_id', $id) + ->get(); + } + + if ($results->isEmpty()) { + return HttpResponse::notFound($request); + } + + + $response = (new MangaRelationsResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } +} diff --git a/app/Http/Controllers/V3/MetaController.php b/app/Http/Controllers/V4DB/MetaController.php old mode 100755 new mode 100644 similarity index 97% rename from app/Http/Controllers/V3/MetaController.php rename to app/Http/Controllers/V4DB/MetaController.php index 87c9d62..113d241 --- a/app/Http/Controllers/V3/MetaController.php +++ b/app/Http/Controllers/V4DB/MetaController.php @@ -1,6 +1,6 @@ where('mal_id', $id) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $response = Person::scrape($id); + + 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()) { + Person::query() + ->insert($response); + } + + if ($this->isExpired($request, $results)) { + Person::query() + ->where('mal_id', $id) + ->update($response); + } + + $results = Person::query() + ->where('mal_id', $id) + ->get(); + } + + if ($results->isEmpty()) { + return HttpResponse::notFound($request); + } + + $response = (new \App\Http\Resources\V4\PersonResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + /** + * @OA\Get( + * path="/people/{id}/anime", + * operationId="getPersonAnime", + * tags={"people"}, + * + * @OA\Response( + * response="200", + * description="Returns person's anime staff positions", + * @OA\JsonContent(ref="#/components/schemas/person anime") + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ) + */ + public function anime(Request $request, int $id) + { + $results = Person::query() + ->where('mal_id', $id) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $response = Person::scrape($id); + + 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()) { + Person::query() + ->insert($response); + } + + if ($this->isExpired($request, $results)) { + Person::query() + ->where('mal_id', $id) + ->update($response); + } + + $results = Person::query() + ->where('mal_id', $id) + ->get(); + } + + if ($results->isEmpty()) { + return HttpResponse::notFound($request); + } + + $response = (new PersonAnimeCollection( + $results->first()['anime_staff_positions'] + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + /** + * @OA\Get( + * path="/people/{id}/voices", + * operationId="getPersonVoices", + * tags={"people"}, + * + * @OA\Response( + * response="200", + * description="Returns person's voice acting roles", + * @OA\JsonContent(ref="#/components/schemas/person voice acting roles") + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ) + */ + public function voices(Request $request, int $id) + { + $results = Person::query() + ->where('mal_id', $id) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $response = Person::scrape($id); + + 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()) { + Person::query() + ->insert($response); + } + + if ($this->isExpired($request, $results)) { + Person::query() + ->where('mal_id', $id) + ->update($response); + } + + $results = Person::query() + ->where('mal_id', $id) + ->get(); + } + + if ($results->isEmpty()) { + return HttpResponse::notFound($request); + } + + $response = (new PersonVoicesCollection( + $results->first()['voice_acting_roles'] + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + /** + * @OA\Get( + * path="/people/{id}/manga", + * operationId="getPersonManga", + * tags={"people"}, + * + * @OA\Response( + * response="200", + * description="Returns person's published manga works", + * @OA\JsonContent(ref="#/components/schemas/person manga") + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ) + */ + public function manga(Request $request, int $id) + { + $results = Person::query() + ->where('mal_id', $id) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $response = Person::scrape($id); + + 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()) { + Person::query() + ->insert($response); + } + + if ($this->isExpired($request, $results)) { + Person::query() + ->where('mal_id', $id) + ->update($response); + } + + $results = Person::query() + ->where('mal_id', $id) + ->get(); + } + + if ($results->isEmpty()) { + return HttpResponse::notFound($request); + } + + $response = (new PersonMangaCollection( + $results->first()['published_manga'] + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + /** + * @OA\Get( + * path="/people/{id}/pictures", + * operationId="getPersonPictures", + * tags={"people"}, + * + * @OA\Parameter( + * name="id", + * in="path", + * required=true, + * @OA\Schema(type="integer") + * ), + * + * @OA\Response( + * response="200", + * description="Returns a list of pictures of the person", + * @OA\JsonContent( + * ref="#/components/schemas/person pictures" + * ) + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ) + * + * @OA\Schema( + * schema="person pictures", + * description="Character Pictures", + * @OA\Property( + * property="data", + * type="array", + * + * @OA\Items( + * @OA\Property( + * property="image_url", + * type="string", + * description="Default JPG Image Size URL" + * ), + * @OA\Property( + * property="large_image_url", + * type="string", + * description="Large JPG Image Size URL" + * ), + * ) + * ) + * ) + */ + public function pictures(Request $request, int $id) + { + $results = DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $person = ['pictures' => $this->jikan->getPersonPictures(new PersonPicturesRequest($id))]; + $response = \json_decode($this->serializer->serialize($person, 'json'), true); + + $results = $this->updateCache($request, $results, $response); + } + + $response = (new PicturesResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } +} diff --git a/app/Http/Controllers/V4DB/ProducerController.php b/app/Http/Controllers/V4DB/ProducerController.php new file mode 100644 index 0000000..b3e77d1 --- /dev/null +++ b/app/Http/Controllers/V4DB/ProducerController.php @@ -0,0 +1,96 @@ +get('page') ?? 1; + $limit = $request->get('limit') ?? self::MAX_RESULTS_PER_PAGE; + + if (!empty($limit)) { + $limit = (int) $limit; + + if ($limit <= 0) { + $limit = 1; + } + + if ($limit > self::MAX_RESULTS_PER_PAGE) { + $limit = self::MAX_RESULTS_PER_PAGE; + } + } + + $results = SearchQueryBuilderProducer::query( + $request, + Producer::query() + ); + + $results = $results + ->paginate( + $limit, + ['*'], + null, + $page + ); + + return new ProducerCollection( + $results + ); + } + + public function resource(Request $request, int $id) + { + $this->request = $request; + $page = $this->request->get('page') ?? 1; + + $results = Anime::query() + ->where('producers.mal_id', $id) + ->orWhere('licensors.mal_id', $id) + ->orWhere('studios.mal_id', $id) + ->orderBy('title'); + + $results = $results + ->paginate( + self::MAX_RESULTS_PER_PAGE, + ['*'], + null, + $page + ); + + return new AnimeCollection( + $results + ); + } +} diff --git a/app/Http/Controllers/V4DB/RandomController.php b/app/Http/Controllers/V4DB/RandomController.php new file mode 100644 index 0000000..df081db --- /dev/null +++ b/app/Http/Controllers/V4DB/RandomController.php @@ -0,0 +1,231 @@ +get('sfw'); + + $results = Anime::query(); + + if (!is_null($sfw)) { + $results = $results + ->where('rating', '!=', 'Rx - Hentai'); + } + + $results = $results + ->raw(fn($collection) => $collection->aggregate([ ['$sample' => ['size' => 1]] ])); + + return new AnimeResource( + $results->first() + ); + } + + /** + * @OA\Get( + * path="/random/manga", + * operationId="getRandomManga", + * tags={"random"}, + * + * @OA\Response( + * response="200", + * description="Returns a random manga resource", + * @OA\JsonContent( + * @OA\Property( + * property="data", + * ref="#/components/schemas/manga" + * ) + * ) + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ), + */ + public function manga(Request $request) + { + $sfw = $request->get('sfw'); + + + $results = Manga::query(); + + if (!is_null($sfw)) { + $results = $results + ->where('type', '!=', 'Doujinshi'); + } + + $results = $results + ->raw(fn($collection) => $collection->aggregate([ ['$sample' => ['size' => 1]] ])); + + return new MangaResource( + $results->first() + ); + } + + /** + * @OA\Get( + * path="/random/characters", + * operationId="getRandomCharacters", + * tags={"random"}, + * + * @OA\Response( + * response="200", + * description="Returns a random character resource", + * @OA\JsonContent( + * @OA\Property( + * property="data", + * ref="#/components/schemas/character" + * ) + * ) + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ), + */ + public function characters(Request $request) + { + $results = Character::query() + ->raw(fn($collection) => $collection->aggregate([ ['$sample' => ['size' => 1]] ])); + + return new CharacterResource( + $results->first() + ); + } + + /** + * @OA\Get( + * path="/random/people", + * operationId="getRandomPeople", + * tags={"random"}, + * + * @OA\Response( + * response="200", + * description="Returns a random person resource", + * @OA\JsonContent( + * @OA\Property( + * property="data", + * ref="#/components/schemas/person" + * ) + * ) + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ), + */ + public function people(Request $request) + { + $results = Person::query() + ->raw(fn($collection) => $collection->aggregate([ ['$sample' => ['size' => 1]] ])); + + return new PersonResource( + $results->first() + ); + } + + /** + * @OA\Get( + * path="/random/users", + * operationId="getRandomUsers", + * tags={"random"}, + * + * @OA\Response( + * response="200", + * description="Returns a random user profile resource", + * @OA\JsonContent( + * @OA\Property( + * property="data", + * ref="#/components/schemas/user profile" + * ) + * ) + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ), + */ + public function users(Request $request) + { + $results = Profile::query() + ->raw(fn($collection) => $collection->aggregate([ ['$sample' => ['size' => 1]] ])); + + return new ProfileResource( + $results->first() + ); + } +} diff --git a/app/Http/Controllers/V4DB/RecommendationsController.php b/app/Http/Controllers/V4DB/RecommendationsController.php new file mode 100644 index 0000000..3830d30 --- /dev/null +++ b/app/Http/Controllers/V4DB/RecommendationsController.php @@ -0,0 +1,114 @@ +getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $page = $request->get('page') ?? 1; + $anime = $this->jikan->getRecentRecommendations(new RecentRecommendationsRequest(Constants::RECENT_RECOMMENDATION_ANIME, $page)); + $response = \json_decode($this->serializer->serialize($anime, 'json'), true); + + $results = $this->updateCache($request, $results, $response); + } + + + $response = (new ResultsResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + /** + * @OA\Get( + * path="/recommendations/manga", + * operationId="getRecentMangaRecommendations", + * tags={"recommendations"}, + * + * @OA\Response( + * response="200", + * description="Returns recent manga recommendations", + * @OA\JsonContent( + * ref="#/components/schemas/recommendations" + * ) + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ), + * + */ + public function manga(Request $request) + { + $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->getRecentRecommendations(new RecentRecommendationsRequest(Constants::RECENT_RECOMMENDATION_MANGA, $page)); + $response = \json_decode($this->serializer->serialize($anime, 'json'), true); + + $results = $this->updateCache($request, $results, $response); + } + + $response = (new ResultsResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } +} diff --git a/app/Http/Controllers/V4DB/ReviewsController.php b/app/Http/Controllers/V4DB/ReviewsController.php new file mode 100644 index 0000000..321de6f --- /dev/null +++ b/app/Http/Controllers/V4DB/ReviewsController.php @@ -0,0 +1,172 @@ +getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->get(); + + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $page = $request->get('page') ?? 1; + $anime = $this->jikan->getRecentReviews(new RecentReviewsRequest(Constants::RECENT_REVIEW_ANIME, $page)); + $response = \json_decode($this->serializer->serialize($anime, 'json'), true); + + $results = $this->updateCache($request, $results, $response); + } + + $response = (new ResultsResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + /** + * @OA\Get( + * path="/reviews/manga", + * operationId="getRecentMangaReviews", + * tags={"reviews"}, + * + * @OA\Response( + * response="200", + * description="Returns recent manga reviews", + * @OA\JsonContent( + * @OA\Property( + * property="data", + * allOf={ + * @OA\Schema(ref="#/components/schemas/pagination"), + * @OA\Schema( + * @OA\Property( + * property="data", + * type="array", + * + * @OA\Items( + * allOf={ + * @OA\Schema(ref="#/components/schemas/manga review"), + * @OA\Schema( + * @OA\Property( + * property="manga", + * type="object", + * ref="#/components/schemas/manga meta", + * ), + * ), + * @OA\Schema( + * @OA\Property( + * property="user", + * type="object", + * ref="#/components/schemas/user meta", + * ), + * ), + * } + * ) + * ), + * ) + * } + * ) + * ) + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ), + */ + public function manga(Request $request) + { + $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->getRecentReviews(new RecentReviewsRequest(Constants::RECENT_REVIEW_MANGA, $page)); + $response = \json_decode($this->serializer->serialize($anime, 'json'), true); + + $results = $this->updateCache($request, $results, $response); + } + + $response = (new ResultsResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } +} diff --git a/app/Http/Controllers/V4DB/ScheduleController.php b/app/Http/Controllers/V4DB/ScheduleController.php new file mode 100644 index 0000000..4f5bcb6 --- /dev/null +++ b/app/Http/Controllers/V4DB/ScheduleController.php @@ -0,0 +1,146 @@ +request = $request; + + $page = $this->request->get('page') ?? 1; + $limit = $this->request->get('limit') ?? env('MAX_RESULTS_PER_PAGE', 25); + + if (!is_null($day)) { + $this->day = strtolower($day); + } + + if (null !== $this->day + && !\in_array($this->day, self::VALID_FILTERS, true)) { + return HttpResponse::badRequest($this->request); + } + + $results = Anime::query() + ->orderBy('members') + ->where('type', 'TV') + ->where('status', 'Currently Airing'); + + if ($this->day !== null && in_array($day, self::VALID_DAYS)) { + $this->day = ucfirst($this->day); + + $results + ->where('broadcast', 'like', "{$day}%"); + } + + if ($this->day === 'unknown') { + $results + ->where('broadcast', 'Unknown'); + } + + if ($this->day === 'other') { + $results + ->where('broadcast', 'Not scheduled once per week'); + } + + $results = $results + ->paginate( + intval($limit), + ['*'], + null, + $page + ); + + $response = (new AnimeCollection( + $results + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } +} diff --git a/app/Http/Controllers/V4DB/SearchController.php b/app/Http/Controllers/V4DB/SearchController.php new file mode 100644 index 0000000..8a3e9f1 --- /dev/null +++ b/app/Http/Controllers/V4DB/SearchController.php @@ -0,0 +1,744 @@ +request = $request; + $page = $this->request->get('page') ?? 1; + $limit = $this->request->get('limit') ?? self::MAX_RESULTS_PER_PAGE; + + if (!empty($limit)) { + $limit = (int) $limit; + + if ($limit <= 0) { + $limit = 1; + } + + if ($limit > self::MAX_RESULTS_PER_PAGE) { + $limit = self::MAX_RESULTS_PER_PAGE; + } + } + + $results = SearchQueryBuilderAnime::query( + $request, + Anime::query() + ); + + $results = $results + ->paginate( + $limit, + ['*'], + null, + $page + ); + + return new AnimeCollection( + $results + ); + } + + /** + * @OA\Get( + * path="/manga", + * operationId="getMangaSearch", + * tags={"manga"}, + * + * @OA\Parameter(ref="#/components/parameters/page"), + * @OA\Parameter(ref="#/components/parameters/limit"), + * + * @OA\Parameter( + * name="q", + * in="query", + * @OA\Schema(type="string") + * ), + * + * @OA\Parameter( + * name="type", + * in="query", + * @OA\Schema(ref="#/components/schemas/manga search query type") + * ), + * + * @OA\Parameter( + * name="score", + * in="query", + * @OA\Schema(type="number") + * ), + * + * @OA\Parameter( + * name="status", + * in="query", + * @OA\Schema(ref="#/components/schemas/manga search query status") + * ), + * + * @OA\Parameter( + * name="sfw", + * in="query", + * description="Filter out Adult entries", + * @OA\Schema(type="boolean") + * ), + * + * @OA\Parameter( + * name="genres", + * in="query", + * description="Filter by genre(s) IDs. Can pass multiple with a comma as a delimiter. e.g 1,2,3", + * @OA\Schema(type="string") + * ), + * + * @OA\Parameter( + * name="order_by", + * in="query", + * @OA\Schema(ref="#/components/schemas/manga search query orderby") + * ), + * + * @OA\Parameter( + * name="sort", + * in="query", + * @OA\Schema(ref="#/components/schemas/search query sort") + * ), + * + * @OA\Parameter( + * name="letter", + * in="query", + * description="Return entries starting with the given letter", + * @OA\Schema(type="string") + * ), + * + * @OA\Parameter( + * name="magazine", + * in="query", + * description="Filter by producer(s) IDs. Can pass multiple with a comma as a delimiter. e.g 1,2,3", + * @OA\Schema(type="string") + * ), + * + * @OA\Response( + * response="200", + * description="Returns search results for manga", + * @OA\JsonContent( + * ref="#/components/schemas/manga search" + * ) + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ) + */ + public function manga(Request $request) + { + $this->request = $request; + $page = $this->request->get('page') ?? 1; + $limit = $this->request->get('limit') ?? self::MAX_RESULTS_PER_PAGE; + + if (!empty($limit)) { + $limit = (int) $limit; + + if ($limit <= 0) { + $limit = 1; + } + + if ($limit > self::MAX_RESULTS_PER_PAGE) { + $limit = self::MAX_RESULTS_PER_PAGE; + } + } + + $results = SearchQueryBuilderManga::query( + $request, + Manga::query() + ); + + $results = $results + ->paginate( + $limit, + ['*'], + null, + $page + ); + + return new MangaCollection( + $results + ); + } + + /** + * @OA\Get( + * path="/people", + * operationId="getPeopleSearch", + * tags={"people"}, + * + * @OA\Parameter(ref="#/components/parameters/page"), + * @OA\Parameter(ref="#/components/parameters/limit"), + * + * @OA\Parameter( + * name="q", + * in="query", + * @OA\Schema(type="string") + * ), + * + * @OA\Parameter( + * name="order_by", + * in="query", + * @OA\Schema(ref="#/components/schemas/people search query orderby") + * ), + * + * @OA\Parameter( + * name="sort", + * in="query", + * @OA\Schema(ref="#/components/schemas/search query sort") + * ), + * + * @OA\Parameter( + * name="letter", + * in="query", + * description="Return entries starting with the given letter", + * @OA\Schema(type="string") + * ), + * + * @OA\Response( + * response="200", + * description="Returns search results for people", + * @OA\JsonContent(ref="#/components/schemas/people search") + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ) + */ + public function people(Request $request) + { + $page = $request->get('page') ?? 1; + $limit = $request->get('limit') ?? self::MAX_RESULTS_PER_PAGE; + + if (!empty($limit)) { + $limit = (int) $limit; + + if ($limit <= 0) { + $limit = 1; + } + + if ($limit > self::MAX_RESULTS_PER_PAGE) { + $limit = self::MAX_RESULTS_PER_PAGE; + } + } + + $results = SearchQueryBuilderPeople::query( + $request, + Person::query() + ); + + $results = $results + ->paginate( + $limit, + ['*'], + null, + $page + ); + + return new PersonCollection( + $results + ); + } + + /** + * @OA\Get( + * path="/characters", + * operationId="getCharactersSearch", + * tags={"characters"}, + * + * @OA\Parameter(ref="#/components/parameters/page"), + * @OA\Parameter(ref="#/components/parameters/limit"), + * + * @OA\Parameter( + * name="q", + * in="query", + * @OA\Schema(type="string") + * ), + * + * @OA\Parameter( + * name="order_by", + * in="query", + * @OA\Schema(ref="#/components/schemas/characters search query orderby") + * ), + * + * @OA\Parameter( + * name="sort", + * in="query", + * @OA\Schema(ref="#/components/schemas/search query sort") + * ), + * + * @OA\Parameter( + * name="letter", + * in="query", + * description="Return entries starting with the given letter", + * @OA\Schema(type="string") + * ), + * + * @OA\Response( + * response="200", + * description="Returns search results for characters", + * @OA\JsonContent( + * ref="#/components/schemas/characters search" + * ) + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ) + */ + public function character(Request $request) + { + $page = $request->get('page') ?? 1; + $limit = $request->get('limit') ?? self::MAX_RESULTS_PER_PAGE; + + if (!empty($limit)) { + $limit = (int) $limit; + + if ($limit <= 0) { + $limit = 1; + } + + if ($limit > self::MAX_RESULTS_PER_PAGE) { + $limit = self::MAX_RESULTS_PER_PAGE; + } + } + + $results = SearchQueryBuilderCharacter::query( + $request, + Character::query() + ); + + $results = $results + ->paginate( + $limit, + ['*'], + null, + $page + ); + + return new CharacterCollection( + $results + ); + } + + /** + * @OA\Get( + * path="/users", + * operationId="getUsersSearch", + * tags={"users"}, + * + * @OA\Parameter(ref="#/components/parameters/page"), + * @OA\Parameter(ref="#/components/parameters/limit"), + * + * @OA\Parameter( + * name="q", + * in="query", + * @OA\Schema(type="string") + * ), + * + * @OA\Parameter( + * name="gender", + * in="query", + * @OA\Schema(ref="#/components/schemas/users search query gender") + * ), + * + * @OA\Parameter( + * name="location", + * in="query", + * @OA\Schema(type="string") + * ), + * + * @OA\Parameter( + * name="maxAge", + * in="query", + * @OA\Schema(type="integer") + * ), + * + * @OA\Parameter( + * name="minAge", + * in="query", + * @OA\Schema(type="integer") + * ), + * + * @OA\Response( + * response="200", + * description="Returns search results for users", + * @OA\JsonContent( + * ref="#/components/schemas/users search" + * ) + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ), + * + * @OA\Schema( + * schema="users search", + * description="User Results", + * + * allOf={ + * @OA\Schema(ref="#/components/schemas/pagination"), + * @OA\Schema( + * @OA\Property( + * property="data", + * type="array", + * + * @OA\Items( + * type="object", + * @OA\Schema( + * @OA\Property( + * property="url", + * type="string", + * description="MyAnimeList URL" + * ), + * @OA\Property( + * property="username", + * type="string", + * description="MyAnimeList Username" + * ), + * @OA\Property( + * ref="#/components/schemas/user images" + * ), + * @OA\Property( + * property="last_online", + * type="string", + * description="Last Online Date ISO8601" + * ), + * ), + * ), + * ), + * ), + * }, + * ), + */ + public function users(Request $request) + { + $results = DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $anime = $this->jikan->getUserSearch( + SearchQueryBuilderUsers::query( + $request + ) + ); + $response = \json_decode($this->serializer->serialize($anime, 'json'), true); + + $results = $this->updateCache($request, $results, $response); + } + + $response = (new ResultsResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + /** + * @OA\Get( + * path="/userbyid", + * operationId="getUserById", + * tags={"users"}, + * + * @OA\Response( + * response="200", + * description="Returns username by ID search", + * @OA\JsonContent( + * ref="#/components/schemas/user by id" + * ) + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ), + * + * + */ + public function userById(Request $request, int $id) + { + $results = DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $anime = ['results'=>$this->jikan->getUsernameById(new UsernameByIdRequest($id))]; + $response = \json_decode($this->serializer->serialize($anime, 'json'), true); + + $results = $this->updateCache($request, $results, $response); + } + + $response = (new ResultsResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + /** + * @OA\Get( + * path="/clubs", + * operationId="getClubsSearch", + * tags={"clubs"}, + * + * @OA\Parameter(ref="#/components/parameters/page"), + * @OA\Parameter(ref="#/components/parameters/limit"), + * + * @OA\Parameter( + * name="q", + * in="query", + * @OA\Schema(type="string") + * ), + * + * @OA\Parameter( + * name="type", + * in="query", + * @OA\Schema(ref="#/components/schemas/club search query type") + * ), + * + * @OA\Parameter( + * name="category", + * in="query", + * @OA\Schema(ref="#/components/schemas/club search query category") + * ), + * + * @OA\Parameter( + * name="order_by", + * in="query", + * @OA\Schema(ref="#/components/schemas/club search query orderby") + * ), + * + * @OA\Parameter( + * name="sort", + * in="query", + * @OA\Schema(ref="#/components/schemas/search query sort") + * ), + * + * @OA\Parameter( + * name="letter", + * in="query", + * description="Return entries starting with the given letter", + * @OA\Schema(type="string") + * ), + * + * @OA\Response( + * response="200", + * description="Returns search results for clubs", + * @OA\JsonContent( + * ref="#/components/schemas/clubs search" + * ) + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ) + */ + public function clubs(Request $request) + { + $this->request = $request; + $page = $this->request->get('page') ?? 1; + $limit = $this->request->get('limit') ?? self::MAX_RESULTS_PER_PAGE; + + if (!empty($limit)) { + $limit = (int) $limit; + + if ($limit <= 0) { + $limit = 1; + } + + if ($limit > self::MAX_RESULTS_PER_PAGE) { + $limit = self::MAX_RESULTS_PER_PAGE; + } + } + + $results = SearchQueryBuilderClub::query( + $request, + Club::query() + ); + + $results = $results + ->paginate( + $limit, + ['*'], + null, + $page + ); + + return new ClubCollection( + $results + ); + } +} diff --git a/app/Http/Controllers/V4DB/SeasonController.php b/app/Http/Controllers/V4DB/SeasonController.php new file mode 100644 index 0000000..df42c0c --- /dev/null +++ b/app/Http/Controllers/V4DB/SeasonController.php @@ -0,0 +1,233 @@ +request = $request; + $page = $this->request->get('page') ?? 1; + $limit = $this->request->get('limit') ?? self::MAX_RESULTS_PER_PAGE; + + if (!empty($limit)) { + $limit = (int) $limit; + + if ($limit <= 0) { + $limit = 1; + } + + if ($limit > self::MAX_RESULTS_PER_PAGE) { + $limit = self::MAX_RESULTS_PER_PAGE; + } + } + + if (!is_null($season)) { + $this->season = ucfirst( + strtolower($season) + ); + } + + if (!is_null($year)) { + $this->year = (int) $year; + } + + if (!is_null($this->season) + && !\in_array($this->season, self::VALID_SEASONS)) { + return HttpResponse::badRequest($this->request); + } + + if (is_null($season) && is_null($year)) { + list($this->season, $this->year) = $this->getSeasonStr(); + } + + $results = Anime::query() + ->where('premiered', "{$this->season} $this->year") + ->orderBy('members', 'desc') + ->paginate( + $limit, + ['*'], + null, + $page); + + return new AnimeCollection( + $results + ); + } + + /** + * @OA\Get( + * path="/seasons", + * operationId="getSeasons", + * tags={"seasons"}, + * + * @OA\Response( + * response="200", + * description="Returns available list of seasons", + * @OA\JsonContent( + * ref="#/components/schemas/seasons" + * ) + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ), + * + * @OA\Schema( + * schema="seasons", + * description="List of available seasons", + * + * @OA\Property( + * property="data", + * type="array", + * + * @OA\Items( + * type="object", + * @OA\Property( + * property="year", + * type="integer", + * description="Year" + * ), + * @OA\Property( + * property="seasons", + * type="array", + * description="List of available seasons", + * @OA\Items( + * type="string" + * ), + * ), + * ), + * ), + * ), + */ + public function archive(Request $request) + { + $results = DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $items = $this->jikan->getSeasonList(new SeasonListRequest()); + $response = \json_decode($this->serializer->serialize($items, 'json'), true); + + $results = $this->updateCache($request, $results, $response); + } + + $response = (new ResultsResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + /** + * @OA\Get( + * path="/seasons/upcoming", + * operationId="getSeasonUpcoming", + * tags={"seasons"}, + * + * @OA\Response( + * response="200", + * description="Returns upcoming season's anime", + * @OA\JsonContent( + * ref="#/components/schemas/anime search" + * ) + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ), + */ + public function later(Request $request) + { + $this->request = $request; + + $nextYear = (new \DateTime(null, new \DateTimeZone('Asia/Tokyo'))) + ->modify('+1 year') + ->format('Y'); + + $results = Anime::query() + ->where('status', 'Not yet aired') + ->where('premiered', 'like', "%{$nextYear}%") + ->orderBy('members', 'desc') + ->get(); + + $this->season = 'Later'; + + return new AnimeCollection( + $results + ); + } + + private function getSeasonStr() : array + { + $date = new \DateTime(null, new \DateTimeZone('Asia/Tokyo')); + + $year = (int) $date->format('Y'); + $month = (int) $date->format('n'); + + switch ($month) { + case \in_array($month, range(1, 3)): + return ['Winter', $year]; + case \in_array($month, range(4, 6)): + return ['Spring', $year]; + case \in_array($month, range(7, 9)): + return ['Summer', $year]; + case \in_array($month, range(10, 12)): + return ['Fall', $year]; + default: throw new \Exception('Could not generate seasonal string'); + } + } +} diff --git a/app/Http/Controllers/V4DB/TopController.php b/app/Http/Controllers/V4DB/TopController.php new file mode 100644 index 0000000..560e466 --- /dev/null +++ b/app/Http/Controllers/V4DB/TopController.php @@ -0,0 +1,366 @@ +get('page') ?? 1; + $limit = $request->get('limit') ?? self::MAX_RESULTS_PER_PAGE; + + if (!empty($limit)) { + $limit = (int) $limit; + + if ($limit <= 0) { + $limit = 1; + } + + if ($limit > self::MAX_RESULTS_PER_PAGE) { + $limit = self::MAX_RESULTS_PER_PAGE; + } + } + + $results = TopQueryBuilderAnime::query( + $request, + Anime::query() + ); + + $results = $results + ->paginate( + $limit, + ['*'], + null, + $page + ); + + return new AnimeCollection( + $results + ); + } + + /** + * @OA\Get( + * path="/top/manga", + * operationId="getTopManga", + * tags={"top"}, + * + * @OA\Response( + * response="200", + * description="Returns top manga", + * @OA\JsonContent( + * ref="#/components/schemas/manga search" + * ) + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ) + */ + public function manga(Request $request) + { + $page = $request->get('page') ?? 1; + $limit = $request->get('limit') ?? self::MAX_RESULTS_PER_PAGE; + + if (!empty($limit)) { + $limit = (int) $limit; + + if ($limit <= 0) { + $limit = 1; + } + + if ($limit > self::MAX_RESULTS_PER_PAGE) { + $limit = self::MAX_RESULTS_PER_PAGE; + } + } + + $results = TopQueryBuilderManga::query( + $request, + Manga::query() + ); + + $results = $results + ->paginate( + $limit, + ['*'], + null, + $page + ); + + return new MangaCollection( + $results + ); + } + + /** + * @OA\Get( + * path="/top/people", + * operationId="getTopPeople", + * tags={"top"}, + * + * @OA\Response( + * response="200", + * description="Returns top people", + * @OA\JsonContent( + * ref="#/components/schemas/people search" + * ) + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ) + */ + public function people(Request $request) + { + $page = $request->get('page') ?? 1; + $limit = $request->get('limit') ?? self::MAX_RESULTS_PER_PAGE; + + if (!empty($limit)) { + $limit = (int) $limit; + + if ($limit <= 0) { + $limit = 1; + } + + if ($limit > self::MAX_RESULTS_PER_PAGE) { + $limit = self::MAX_RESULTS_PER_PAGE; + } + } + + $results = Person::query() + ->whereNotNull('member_favorites') + ->where('member_favorites', '>', 0) + ->orderBy('member_favorites', 'desc'); + + $results = $results + ->paginate( + $limit, + ['*'], + null, + $page + ); + + return new PersonCollection( + $results + ); + } + + /** + * @OA\Get( + * path="/top/characters", + * operationId="getTopCharacters", + * tags={"top"}, + * + * @OA\Response( + * response="200", + * description="Returns top characters", + * @OA\JsonContent( + * ref="#/components/schemas/characters search" + * ) + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ) + */ + public function characters(Request $request) + { + $page = $request->get('page') ?? 1; + $limit = $request->get('limit') ?? self::MAX_RESULTS_PER_PAGE; + + if (!empty($limit)) { + $limit = (int) $limit; + + if ($limit <= 0) { + $limit = 1; + } + + if ($limit > self::MAX_RESULTS_PER_PAGE) { + $limit = self::MAX_RESULTS_PER_PAGE; + } + } + + $results = Character::query() + ->whereNotNull('member_favorites') + ->where('member_favorites', '>', 0) + ->orderBy('member_favorites', 'desc'); + + $results = $results + ->paginate( + $limit, + ['*'], + null, + $page + ); + + return new CharacterCollection( + $results + ); + } + + /** + * @OA\Get( + * path="/top/reviews", + * operationId="getTopReviews", + * tags={"top"}, + * + * @OA\Response( + * response="200", + * description="Returns top reviews", + * @OA\JsonContent( + * @OA\Property( + * property="data", + * allOf={ + * @OA\Schema(ref="#/components/schemas/pagination"), + * @OA\Schema( + * @OA\Property( + * property="data", + * type="array", + * + * @OA\Items( + * anyOf={ + * @OA\Schema( + * allOf={ + * @OA\Schema(ref="#/components/schemas/anime review"), + * @OA\Schema( + * @OA\Property( + * property="anime", + * type="object", + * ref="#/components/schemas/anime meta", + * ), + * ), + * @OA\Schema( + * @OA\Property( + * property="user", + * type="object", + * ref="#/components/schemas/user meta", + * ), + * ), + * }, + * ), + * @OA\Schema( + * allOf={ + * @OA\Schema(ref="#/components/schemas/manga review"), + * @OA\Schema( + * @OA\Property( + * property="manga", + * type="object", + * ref="#/components/schemas/manga meta", + * ), + * ), + * @OA\Schema( + * @OA\Property( + * property="user", + * type="object", + * ref="#/components/schemas/user meta", + * ), + * ), + * }, + * ), + * }, + * ), + * ), + * ) + * } + * ) + * ), + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ), + * + * @OA\Schema( + * schema="reviews collection", + * description="Anime & Manga Reviews Resource", + * + * @OA\Property( + * property="data", + * type="array", + * + * @OA\Items( + * type="object", + * anyOf = { + * @OA\Schema(ref="#/components/schemas/anime review"), + * @OA\Schema(ref="#/components/schemas/manga review"), + * }, + * ), + * ), + * ), + */ + public function reviews(Request $request) + { + $results = DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $page = $request->get('page') ?? 1; + $data = $this->jikan->getRecentReviews( + new RecentReviewsRequest(Constants::RECENT_REVIEW_BEST_VOTED, $page) + ); + $response = \json_decode($this->serializer->serialize($data, 'json'), true); + + $results = $this->updateCache($request, $results, $response); + } + + $response = (new ResultsResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } +} diff --git a/app/Http/Controllers/V4DB/UserController.php b/app/Http/Controllers/V4DB/UserController.php new file mode 100644 index 0000000..be19b21 --- /dev/null +++ b/app/Http/Controllers/V4DB/UserController.php @@ -0,0 +1,1074 @@ +where('internal_username', $username) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $response = Profile::scrape($username); + + if ($results->isEmpty()) { + $meta = [ + 'createdAt' => new UTCDateTime(), + 'modifiedAt' => new UTCDateTime(), + 'request_hash' => $this->fingerprint, + 'internal_username' => $username + ]; + } + $meta['modifiedAt'] = new UTCDateTime(); + + $response = $meta + $response; + + if ($results->isEmpty()) { + Profile::query() + ->insert($response); + } + + if ($this->isExpired($request, $results)) { + Profile::query() + ->where('internal_username', $username) + ->update($response); + } + + $results = Profile::query() + ->where('internal_username', $username) + ->get(); + } + + if ($results->isEmpty()) { + return HttpResponse::notFound($request); + } + + $response = (new \App\Http\Resources\V4\ProfileResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + /** + * @OA\Get( + * path="/users/{username}/statistics", + * operationId="getUserStatistics", + * tags={"users"}, + * + * @OA\Parameter( + * name="username", + * in="path", + * required=true, + * @OA\Schema(type="string") + * ), + * + * @OA\Response( + * response="200", + * description="Returns user statistics", + * @OA\JsonContent( + * ref="#/components/schemas/user statistics" + * ) + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ), + */ + public function statistics(Request $request, string $username) + { + + $username = strtolower($username); + + $results = Profile::query() + ->where('internal_username', $username) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $response = Profile::scrape($username); + + if ($results->isEmpty()) { + $meta = [ + 'createdAt' => new UTCDateTime(), + 'modifiedAt' => new UTCDateTime(), + 'request_hash' => $this->fingerprint, + 'internal_username' => $username + ]; + } + $meta['modifiedAt'] = new UTCDateTime(); + + $response = $meta + $response; + + if ($results->isEmpty()) { + Profile::query() + ->insert($response); + } + + if ($this->isExpired($request, $results)) { + Profile::query() + ->where('internal_username', $username) + ->update($response); + } + + $results = Profile::query() + ->where('internal_username', $username) + ->get(); + } + + if ($results->isEmpty()) { + return HttpResponse::notFound($request); + } + + $response = (new \App\Http\Resources\V4\ProfileStatisticsResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + + /** + * @OA\Get( + * path="/users/{username}/favorites", + * operationId="getUserFavorites", + * tags={"users"}, + * + * @OA\Parameter( + * name="username", + * in="path", + * required=true, + * @OA\Schema(type="string") + * ), + * + * @OA\Response( + * response="200", + * description="Returns user favorites", + * @OA\JsonContent( + * ref="#/components/schemas/user favorites" + * ) + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ), + */ + public function favorites(Request $request, string $username) + { + + $username = strtolower($username); + + $results = Profile::query() + ->where('internal_username', $username) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $response = Profile::scrape($username); + + if ($results->isEmpty()) { + $meta = [ + 'createdAt' => new UTCDateTime(), + 'modifiedAt' => new UTCDateTime(), + 'request_hash' => $this->fingerprint, + 'internal_username' => $username + ]; + } + $meta['modifiedAt'] = new UTCDateTime(); + + $response = $meta + $response; + + if ($results->isEmpty()) { + Profile::query() + ->insert($response); + } + + if ($this->isExpired($request, $results)) { + Profile::query() + ->where('internal_username', $username) + ->update($response); + } + + $results = Profile::query() + ->where('internal_username', $username) + ->get(); + } + + if ($results->isEmpty()) { + return HttpResponse::notFound($request); + } + + $response = (new \App\Http\Resources\V4\ProfileFavoritesResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + /** + * @OA\Get( + * path="/users/{username}/userupdates", + * operationId="getUserUpdates", + * tags={"users"}, + * + * @OA\Parameter( + * name="username", + * in="path", + * required=true, + * @OA\Schema(type="string") + * ), + * + * @OA\Response( + * response="200", + * description="Returns user updates", + * @OA\JsonContent( + * ref="#/components/schemas/user updates" + * ) + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ), + */ + public function userupdates(Request $request, string $username) + { + + $username = strtolower($username); + + $results = Profile::query() + ->where('internal_username', $username) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $response = Profile::scrape($username); + + if ($results->isEmpty()) { + $meta = [ + 'createdAt' => new UTCDateTime(), + 'modifiedAt' => new UTCDateTime(), + 'request_hash' => $this->fingerprint, + 'internal_username' => $username + ]; + } + $meta['modifiedAt'] = new UTCDateTime(); + + $response = $meta + $response; + + if ($results->isEmpty()) { + Profile::query() + ->insert($response); + } + + if ($this->isExpired($request, $results)) { + Profile::query() + ->where('internal_username', $username) + ->update($response); + } + + $results = Profile::query() + ->where('internal_username', $username) + ->get(); + } + + if ($results->isEmpty()) { + return HttpResponse::notFound($request); + } + + $response = (new \App\Http\Resources\V4\ProfileLastUpdatesResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + /** + * @OA\Get( + * path="/users/{username}/about", + * operationId="getUserAbout", + * tags={"users"}, + * + * @OA\Parameter( + * name="username", + * in="path", + * required=true, + * @OA\Schema(type="string") + * ), + * + * @OA\Response( + * response="200", + * description="Returns user about in raw HTML", + * @OA\JsonContent( + * ref="#/components/schemas/user about" + * ) + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ), + */ + public function about(Request $request, string $username) + { + + $username = strtolower($username); + + $results = Profile::query() + ->where('internal_username', $username) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $response = Profile::scrape($username); + + if ($results->isEmpty()) { + $meta = [ + 'createdAt' => new UTCDateTime(), + 'modifiedAt' => new UTCDateTime(), + 'request_hash' => $this->fingerprint, + 'internal_username' => $username + ]; + } + $meta['modifiedAt'] = new UTCDateTime(); + + $response = $meta + $response; + + if ($results->isEmpty()) { + Profile::query() + ->insert($response); + } + + if ($this->isExpired($request, $results)) { + Profile::query() + ->where('internal_username', $username) + ->update($response); + } + + $results = Profile::query() + ->where('internal_username', $username) + ->get(); + } + + if ($results->isEmpty()) { + return HttpResponse::notFound($request); + } + + $response = (new \App\Http\Resources\V4\ProfileAboutResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + /** + * @OA\Get( + * path="/users/{username}/history/{type}", + * operationId="getUserHistory", + * tags={"users"}, + * + * @OA\Parameter( + * name="username", + * in="path", + * required=true, + * @OA\Schema(type="string") + * ), + * + * @OA\Parameter( + * name="type", + * in="path", + * required=false, + * @OA\Schema(type="string",enum={"anime", "manga"}) + * ), + * + * @OA\Response( + * response="200", + * description="Returns user history (past 30 days)", + * @OA\JsonContent( + * ref="#/components/schemas/user history" + * ) + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ), + */ + public function history(Request $request, string $username, ?string $type = null) + { + if (!is_null($type)) { + $type = strtolower($type); + } + + if (!is_null($type) && !\in_array($type, ['anime', 'manga'])) { + return HttpResponse::badRequest($request); + } + + $results = DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $data = ['history'=>$this->jikan->getUserHistory(new UserHistoryRequest($username, $type))]; + $response = \json_decode($this->serializer->serialize($data, 'json'), true); + + $results = $this->updateCache($request, $results, $response); + } + + $response = (new ProfileHistoryResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + /** + * @OA\Get( + * path="/users/{username}/friends", + * operationId="getUserFriends", + * tags={"users"}, + * + * @OA\Parameter( + * name="username", + * in="path", + * required=true, + * @OA\Schema(type="string") + * ), + * + * @OA\Response( + * response="200", + * description="Returns user friends", + * @OA\JsonContent( + * ref="#/components/schemas/user friends" + * ) + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ), + * + * @OA\Schema( + * schema="user friends", + * description="User Friends", + * + * allOf={ + * @OA\Schema(ref="#/components/schemas/pagination"), + * @OA\Schema( + * + * @OA\Property( + * property="data", + * type="array", + * + * @OA\Items( + * type="object", + * + * allOf={ + * @OA\Schema( + * + * @OA\Property( + * property="user", + * type="object", + * ref="#/components/schemas/user meta" + * ), + * ), + * @OA\Schema ( + * @OA\Property( + * property="last_online", + * type="string", + * description="Last Online Date ISO8601 format" + * ), + * @OA\Property( + * property="friends_since", + * type="string", + * description="Friends Since Date ISO8601 format" + * ), + * ), + * }, + * ), + * ), + * ), + * } + * ), + */ + public function friends(Request $request, string $username) + { + $results = DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $page = $request->get('page') ?? 1; + $data = $this->jikan->getUserFriends(new UserFriendsRequest($username, $page)); + $response = \json_decode($this->serializer->serialize($data, 'json'), true); + + $results = $this->updateCache($request, $results, $response); + } + + $response = (new ResultsResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + /** + * @OA\Get( + * path="/users/{username}/animelist", + * operationId="getUserAnimelist", + * tags={"users"}, + * + * @OA\Parameter( + * name="username", + * in="path", + * required=true, + * @OA\Schema(type="string") + * ), + * + * @OA\Response( + * response="200", + * description="Returns user anime list", + * @OA\JsonContent( + * ) + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ), + * + */ + public function animelist(Request $request, string $username, ?string $status = null) + { + if (!is_null($status)) { + $status = strtolower($status); + + if (!\in_array($status, ['all', 'watching', 'completed', 'onhold', 'dropped', 'plantowatch'])) { + return HttpResponse::badRequest($request); + } + } + $status = $this->listStatusToId($status); + + + $results = DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $page = $request->get('page') ?? 1; + $data = $this->jikan->getUserAnimeList( + UserListQueryBuilder::create( + $request, + new UserAnimeListRequest($username, $page, $status) + ) + ); + $response = ['anime' => \json_decode($this->serializer->serialize($data, 'json'), true)]; + + $results = $this->updateCache($request, $results, $response); + } + + $listResults = $results->first()['anime']; + + foreach ($listResults as &$result) { + $result = (new UserProfileAnimeListResource($result)); + } + + $response = (new UserProfileAnimeListCollection( + $listResults + ))->response($request); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + /** + * @OA\Get( + * path="/users/{username}/mangalist", + * operationId="getUserMangaList", + * tags={"users"}, + * + * @OA\Parameter( + * name="username", + * in="path", + * required=true, + * @OA\Schema(type="string") + * ), + * + * @OA\Response( + * response="200", + * description="Returns user manga list", + * @OA\JsonContent( + * ) + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ), + * + */ + public function mangalist(Request $request, string $username, ?string $status = null) + { + if (!is_null($status)) { + $status = strtolower($status); + + if (!\in_array($status, ['all', 'reading', 'completed', 'onhold', 'dropped', 'plantoread', 'ptr'])) { + return response()->json([ + 'error' => 'Bad Request' + ])->setStatusCode(400); + } + } + $status = $this->listStatusToId($status); + + $results = DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $page = $request->get('page') ?? 1; + $data = $this->jikan->getUserMangaList( + UserListQueryBuilder::create( + $request, + new UserMangaListRequest($username, $page, $status) + ) + ); + $response = ['manga' => \json_decode($this->serializer->serialize($data, 'json'), true)]; + + $results = $this->updateCache($request, $results, $response); + } + + $listResults = $results->first()['manga']; + + foreach ($listResults as &$result) { + $result = (new UserProfileMangaListResource($result)); + } + + $response = (new UserProfileMangaListCollection( + $listResults + ))->response($request); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + /** + * @OA\Get( + * path="/users/{username}/reviews", + * operationId="getUserReviews", + * tags={"users"}, + * + * @OA\Parameter( + * name="username", + * in="path", + * required=true, + * @OA\Schema(type="string") + * ), + * + * @OA\Response( + * response="200", + * description="Returns user reviews", + * @OA\JsonContent( + * @OA\Property( + * property="data", + * allOf={ + * @OA\Schema(ref="#/components/schemas/pagination"), + * @OA\Schema( + * @OA\Property( + * property="data", + * type="array", + * + * @OA\Items( + * anyOf={ + * @OA\Schema( + * allOf={ + * @OA\Schema(ref="#/components/schemas/anime review"), + * @OA\Schema( + * @OA\Property( + * property="anime", + * type="object", + * ref="#/components/schemas/anime meta", + * ), + * ), + * @OA\Schema( + * @OA\Property( + * property="user", + * type="object", + * ref="#/components/schemas/user meta", + * ), + * ), + * }, + * ), + * @OA\Schema( + * allOf={ + * @OA\Schema(ref="#/components/schemas/manga review"), + * @OA\Schema( + * @OA\Property( + * property="manga", + * type="object", + * ref="#/components/schemas/manga meta", + * ), + * ), + * @OA\Schema( + * @OA\Property( + * property="user", + * type="object", + * ref="#/components/schemas/user meta", + * ), + * ), + * }, + * ), + * }, + * ), + * ), + * ) + * } + * ) + * ), + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ), + */ + public function reviews(Request $request, string $username) + { + $results = DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $page = $request->get('page') ?? 1; + $data = $this->jikan->getUserReviews(new UserReviewsRequest($username, $page)); + $response = \json_decode($this->serializer->serialize($data, 'json'), true); + + $results = $this->updateCache($request, $results, $response); + } + + $response = (new ResultsResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + /** + * @OA\Get( + * path="/users/{username}/recommendations", + * operationId="getUserRecommendations", + * tags={"users"}, + * + * @OA\Parameter( + * name="username", + * in="path", + * required=true, + * @OA\Schema(type="string") + * ), + * + * @OA\Response( + * response="200", + * description="Returns Recent Anime Recommendations", + * @OA\JsonContent(ref="#/components/schemas/recommendations") + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ), + * + */ + public function recommendations(Request $request, string $username) + { + $results = DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $page = $request->get('page') ?? 1; + $data = $this->jikan->getUserRecommendations(new UserRecommendationsRequest($username, $page)); + $response = \json_decode($this->serializer->serialize($data, 'json'), true); + + $results = $this->updateCache($request, $results, $response); + } + + $response = (new ResultsResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + /** + * @OA\Get( + * path="/users/{username}/clubs", + * operationId="getUserClubs", + * tags={"users"}, + * + * @OA\Parameter( + * name="username", + * in="path", + * required=true, + * @OA\Schema(type="string") + * ), + * + * @OA\Response( + * response="200", + * description="Returns user clubs", + * @OA\JsonContent(ref="#/components/schemas/user clubs") + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ), + * + * @OA\Schema( + * schema="user clubs", + * description="User Clubs", + * + * allOf={ + * @OA\Schema(ref="#/components/schemas/pagination"), + * @OA\Schema( + * + * @OA\Property( + * property="data", + * type="array", + * @OA\Items( + * type="object", + * + * @OA\Property( + * property="mal_id", + * type="integer", + * description="MyAnimeList ID" + * ), + * @OA\Property( + * property="name", + * type="string", + * description="Club Name" + * ), + * @OA\Property( + * property="url", + * type="string", + * description="Club URL" + * ), + * ), + * ), + * ), + * } + * ), + */ + public function clubs(Request $request, string $username) + { + $results = DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $data = ['results' => $this->jikan->getUserClubs(new UserClubsRequest($username))]; + $response = \json_decode($this->serializer->serialize($data, 'json'), true); + + $results = $this->updateCache($request, $results, $response); + } + + $response = (new ResultsResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + public function recentlyOnline(Request $request) + { + $results = DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $data = ['results'=>$this->jikan->getRecentOnlineUsers(new RecentlyOnlineUsersRequest())]; + $response = \json_decode($this->serializer->serialize($data, 'json'), true); + + $results = $this->updateCache($request, $results, $response); + } + + $response = (new ResultsResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + private function listStatusToId(?string $status) : int + { + if (is_null($status)) { + return 7; + } + + switch ($status) { + case 'all': + return 7; + case 'watching': + case 'reading': + return 1; + case 'completed': + return 2; + case 'onhold': + return 3; + case 'dropped': + return 4; + case 'plantowatch': + case 'ptw': + case 'plantoread': + case 'ptr': + return 6; + default: + return 7; + } + } +} diff --git a/app/Http/Controllers/V4DB/WatchController.php b/app/Http/Controllers/V4DB/WatchController.php new file mode 100644 index 0000000..4fa2292 --- /dev/null +++ b/app/Http/Controllers/V4DB/WatchController.php @@ -0,0 +1,308 @@ +getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $items = $this->jikan->getRecentEpisodes(new RecentEpisodesRequest()); + $response = \json_decode($this->serializer->serialize($items, 'json'), true); + + $results = $this->updateCache($request, $results, $response); + } + + $response = (new ResultsResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + /** + * @OA\Get( + * path="/watch/episodes/popular", + * operationId="getWatchPopularEpisodes", + * tags={"watch"}, + * + * @OA\Response( + * response="200", + * description="Returns Popular Episodes", + * @OA\JsonContent( + * ref="#/components/schemas/watch episodes" + * ) + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ), + */ + public function popularEpisodes(Request $request) + { + $results = DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $items = $this->jikan->getPopularEpisodes(new PopularEpisodesRequest()); + $response = \json_decode($this->serializer->serialize($items, 'json'), true); + + $results = $this->updateCache($request, $results, $response); + } + + $response = (new ResultsResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + /** + * @OA\Get( + * path="/watch/promos", + * operationId="getWatchRecentPromos", + * tags={"watch"}, + * + * @OA\Response( + * response="200", + * description="Returns Recently Added Promotional Videos", + * @OA\JsonContent( + * ref="#/components/schemas/watch promos" + * ) + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ), + * + * @OA\Schema( + * schema="watch promos", + * description="Watch Promos", + * + * allOf={ + * @OA\Schema(ref="#/components/schemas/pagination"), + * @OA\Schema( + * + * allOf={ + * @OA\Schema( + * @OA\Property( + * property="title", + * type="string", + * description="Promo Title" + * ), + * ), + * @OA\Schema ( + * @OA\Property( + * property="data", + * type="array", + * + * @OA\Items( + * type="object", + * @OA\Property( + * property="entry", + * type="object", + * ref="#/components/schemas/anime meta" + * ), + * @OA\Property( + * property="trailer", + * type="array", + * @OA\Items( + * type="object", + * ref="#/components/schemas/trailer", + * ), + * ), + * ), + * ), + * ), + * }, + * ), + * }, + * ), + */ + public function recentPromos(Request $request) + { + $results = DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $page = $request->get('page') ?? 1; + $items = $this->jikan->getRecentPromotionalVideos(new RecentPromotionalVideosRequest($page)); + $response = \json_decode($this->serializer->serialize($items, 'json'), true); + + $results = $this->updateCache($request, $results, $response); + } + + $response = (new ResultsResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + + /** + * @OA\Get( + * path="/watch/promos/popular", + * operationId="getWatchPopularPromos", + * tags={"watch"}, + * + * @OA\Response( + * response="200", + * description="Returns Popular Promotional Videos", + * @OA\JsonContent( + * ref="#/components/schemas/watch promos" + * ) + * ), + * @OA\Response( + * response="400", + * description="Error: Bad request. When required parameters were not supplied.", + * ), + * ), + */ + public function popularPromos(Request $request) + { + $results = DB::table($this->getRouteTable($request)) + ->where('request_hash', $this->fingerprint) + ->get(); + + if ( + $results->isEmpty() + || $this->isExpired($request, $results) + ) { + $items = $this->jikan->getPopularPromotionalVideos(new PopularPromotionalVideosRequest()); + $response = \json_decode($this->serializer->serialize($items, 'json'), true); + + $results = $this->updateCache($request, $results, $response); + } + + $response = (new ResultsResource( + $results->first() + ))->response(); + + return $this->prepareResponse( + $response, + $results, + $request + ); + } + +} diff --git a/app/Http/HttpHelper.php b/app/Http/HttpHelper.php index 6ddf148..046a2fc 100755 --- a/app/Http/HttpHelper.php +++ b/app/Http/HttpHelper.php @@ -19,7 +19,7 @@ class HttpHelper public static function requestType(Request $request): string { $requestType = $request->segments()[1]; - if (!\in_array($request->segments()[0], ['v1', 'v2', 'v3'])) { + if (!\in_array($request->segments()[0], ['v1', 'v2', 'v3', 'v4'])) { $requestType = $request->segments()[0]; } @@ -54,7 +54,7 @@ class HttpHelper foreach ($related as $relation => $items) { $data['related'][] = [ 'relation' => $relation, - 'items' => $items + 'entry' => $items ]; } } @@ -75,7 +75,7 @@ class HttpHelper foreach ($related as $relation => $items) { $data['related'][] = [ 'relation' => $relation, - 'items' => $items + 'entry' => $items ]; } } @@ -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/HttpResponse.php b/app/Http/HttpResponse.php new file mode 100644 index 0000000..ff4a5b1 --- /dev/null +++ b/app/Http/HttpResponse.php @@ -0,0 +1,36 @@ + 404, + 'type' => 'BadResponseException', + 'message' => 'Resource not found', + 'error' => '404 on ' . $request->getUri() + ]), + 404 + ); + } + + public static function badRequest(Request $request) : Response + { + return response( + \json_encode([ + 'status' => 400, + 'type' => 'BadRequestException', + 'message' => 'Invalid or incomplete request. Make sure your request is correct. https://docs.api.jikan.moe/', + 'error' => null + ]), + 400 + ); + } +} \ No newline at end of file diff --git a/app/Http/Middleware/JikanResponseHandler.php b/app/Http/Middleware/CacheResolver.php old mode 100755 new mode 100644 similarity index 92% rename from app/Http/Middleware/JikanResponseHandler.php rename to app/Http/Middleware/CacheResolver.php index 7446050..cd0189e --- a/app/Http/Middleware/JikanResponseHandler.php +++ b/app/Http/Middleware/CacheResolver.php @@ -1,19 +1,5 @@ header('auth') === env('APP_KEY')) { return $next($request); } diff --git a/app/Http/Middleware/EtagMiddleware.php b/app/Http/Middleware/EtagMiddleware.php deleted file mode 100644 index f91cc6b..0000000 --- a/app/Http/Middleware/EtagMiddleware.php +++ /dev/null @@ -1,48 +0,0 @@ -header('auth') === env('APP_KEY')) { - return $next($request); - } - - if (empty($request->segments())) { - return $next($request); - } - - if (!isset($request->segments()[1])) { - return $next($request); - } - - if (\in_array('meta', $request->segments())) { - return $next($request); - } - - $fingerprint = HttpHelper::resolveRequestFingerprint($request); - - if ( - $request->hasHeader('If-None-Match') - && Cache::has($fingerprint) - && md5(Cache::get($fingerprint)) === $request->header('If-None-Match') - ) { - return response('', 304); - } - - return $next($request); - } -} diff --git a/app/Http/Middleware/Insights.php b/app/Http/Middleware/Insights.php new file mode 100644 index 0000000..332cd11 --- /dev/null +++ b/app/Http/Middleware/Insights.php @@ -0,0 +1,52 @@ +original['error'])) { + return; + } + + // @todo scaling: implement as scheduled event if needed + // Delete requests older than INSIGHTS_MAX_STORE + DB::table('insights') + ->where('timestamp', '<', time() - env('INSIGHTS_MAX_STORE_TIME', 172800) ) + ->delete(); + + DB::table('insights') + ->insert([ + 'timestamp' => time(), + 'url' => $request->getRequestUri(), + 'type' => + ]); + } + +} diff --git a/app/Http/Middleware/MicroCaching.php b/app/Http/Middleware/MicroCaching.php index 32dec20..6e5f4d4 100644 --- a/app/Http/Middleware/MicroCaching.php +++ b/app/Http/Middleware/MicroCaching.php @@ -4,10 +4,20 @@ namespace App\Http\Middleware; use App\Http\HttpHelper; use Closure; -use Illuminate\Support\Facades\Redis; +use Illuminate\Support\Facades\Cache; +use Jikan\Exception\BadResponseException; class MicroCaching { + private const NO_CACHING = [ + 'RandomController@anime', + 'RandomController@manga', + 'RandomController@characters', + 'RandomController@people', + 'RandomController@users', + 'InsightsController@main' + ]; + /** * Handle an incoming request. * @@ -17,31 +27,54 @@ class MicroCaching */ public function handle($request, Closure $next) { + if (isset($request->route()[1]['uses'])) { + $route = explode('\\', $request->route()[1]['uses']); + $route = end($route); + if (\in_array($route, self::NO_CACHING)) { + return $next($request); + } + } + if ($request->header('auth') === env('APP_KEY')) { return $next($request); } - // Microcaching should not work alongside redis caching - if (!env('MICROCACHING', false) || env('CACHE_DRIVER', 'file') === 'redis') { + if ( + empty($request->segments()) + || !isset($request->segments()[1]) + ) { + return $next($request); + } + + if ( + !env('CACHING') + || !env('MICROCACHING') + || env('CACHE_DRIVER') !== 'redis' + ) { return $next($request); } $fingerprint = "microcache:".HttpHelper::resolveRequestFingerprint($request); + + // if cache exists, return cache if (app('redis')->exists($fingerprint)) { return response() ->json( - json_decode(app('redis')->get($fingerprint), true) + \json_decode(app('redis')->get($fingerprint), true) ); } + // set cache + app('redis')->set( + $fingerprint, + json_encode( + $next($request)->getData() + ) + ); + + app('redis')->expire($fingerprint, env('MICROCACHING_EXPIRE', 5)); + return $next($request); } - public static function setMicroCache($fingerprint, $cache) { - $fingerprint = "microcache:".$fingerprint; - $cache = json_encode($cache); - - app('redis')->set($fingerprint, $cache); - app('redis')->expire($fingerprint, env('MICROCACHING_EXPIRE', 5)); - } } diff --git a/app/Http/Middleware/ResourceNotFoundCacheMiddleware.php b/app/Http/Middleware/ResourceNotFoundCacheMiddleware.php deleted file mode 100755 index b3d9bbc..0000000 --- a/app/Http/Middleware/ResourceNotFoundCacheMiddleware.php +++ /dev/null @@ -1 +0,0 @@ -header('auth') === env('APP_KEY')) { + return $next($request); + } + if (!env('THROTTLE', false)) { return $next($request); } diff --git a/app/Http/QueryBuilder/SearchQueryBuilderAnime.php b/app/Http/QueryBuilder/SearchQueryBuilderAnime.php new file mode 100644 index 0000000..25b673a --- /dev/null +++ b/app/Http/QueryBuilder/SearchQueryBuilderAnime.php @@ -0,0 +1,317 @@ + 'TV', + 'movie' => 'Movie', + 'ova' => 'OVA', + 'special' => 'Special', + 'ona' => 'ONA', + 'music' => 'Music' + ]; + + /** + * @OA\Schema( + * schema="anime search query status", + * description="Available Anime statuses", + * type="string", + * enum={"airing","complete","upcoming"} + * ) + */ + const MAP_STATUS = [ + 'airing' => 'Currently Airing', + 'complete' => 'Finished Airing', + 'upcoming' => 'Not yet aired', + ]; + + /** + * @OA\Schema( + * schema="anime search query rating", + * description="Available Anime audience ratings

Ratings
", + * type="string", + * enum={"g","pg","pg13","r17","r","rx"} + * ) + */ + const MAP_RATING = [ + 'g' => 'G - All Ages', + 'pg' => 'PG - Children', + 'pg13' => 'PG-13 - Teens 13 or older', + 'r17' => 'R - 17+ (violence & profanity)', + 'r' => 'R+ - Mild Nudity', + 'rx' => 'Rx - Hentai' + ]; + + /** + * @OA\Schema( + * schema="anime search query orderby", + * description="Available Anime order_by properties", + * type="string", + * enum={"mal_id", "title", "type", "rating", "start_date", "end_date", "episodes", "score", "scored_by", "rank", "popularity", "members", "favorites" } + * ) + */ + const ORDER_BY = [ + 'mal_id' => 'mal_id', + 'title' => 'title', + 'type' => 'type', + 'rating' => 'rating', + 'start_date' => 'aired.from', + 'end_date' => 'aired.to', + 'episodes' => 'episodes', + 'score' => 'score', + 'scored_by' => 'scored_by', + 'rank' => 'rank', + 'popularity' => 'popularity', + 'members' => 'members', + 'favorites' => 'favorites' + ]; + + /** + * @param Request $request + * @param Builder $results + * @return Builder + */ + public static function query(Request $request, Builder $results) : Builder + { + $requestType = HttpHelper::requestType($request); + $query = $request->get('q'); + $type = self::mapType($request->get('type')); + $score = $request->get('score') ?? 0; + $status = self::mapStatus($request->get('status')); + $rating = self::mapRating($request->get('rating')); + $sfw = $request->get('sfw'); + $genres = $request->get('genres'); + $orderBy = self::mapOrderBy($request->get('order_by')); + $sort = self::mapSort($request->get('sort')); + $letter = $request->get('letter'); + $producer = $request->get('producers'); + $minScore = $request->get('min_score'); + $maxScore = $request->get('max_score'); + + if (!empty($query) && is_null($letter)) { + + $results = $results + ->where('title', 'like', "%{$query}%") + ->orWhere('title_english', 'like', "%{$query}%") + ->orWhere('title_japanese', 'like', "%{$query}%") + ->orWhere('title_synonyms', 'like', "%{$query}%"); + + // needs elastic search +// $results = $results +// ->whereRaw([ +// '$text' => [ +// '$search' => $query +// ] +// ]); + } + + if (!is_null($letter)) { + $results = $results + ->where('title', 'like', "{$letter}%"); + } + + if (empty($query) && is_null($orderBy)) { + $results = $results + ->orderBy('mal_id'); + } + + if (!is_null($type)) { + $results = $results + ->where('type', $type); + } + + if ($score !== 0) { + $score = (float) $score; + + $results = $results + ->where('score', '>=', $score); + } + + if ($minScore !== null) { + $minScore = (float) $minScore; + + $results = $results + ->where('score', '>=', $minScore); + } + + if ($maxScore !== null) { + $maxScore = (float) $maxScore; + + $results = $results + ->where('score', '<=', $maxScore); + } + + if (!is_null($status)) { + $results = $results + ->where('status', $status); + } + + if (!is_null($rating)) { + $results = $results + ->where('rating', $rating); + } + + if (!is_null($sfw)) { + $results = $results + ->where('rating', '!=', self::MAP_RATING['rx']); + } + + if (!is_null($producer)) { + + $producer = (int) $producer; + + $results = $results + ->where('producers.mal_id', $producer) + ->orWhere('licensors.mal_id', $producer) + ->orWhere('studios.mal_id', $producer); + } + + if (!is_null($genres)) { + $genres = explode(',', $genres); + + foreach ($genres as $genre) { + if (empty($genre)) { + continue; + } + + $genre = (int) $genre; + + $results = $results + ->where('genres.mal_id', $genre); + } + } + + if (!is_null($orderBy)) { + $results = $results + ->orderBy($orderBy, $sort ?? 'asc'); + } + + return $results; + } + + /** + * @param Request $request + * @param Builder $results + * @return array + */ + public static function paginate(Request $request, Builder $results) + { + $page = $request->get('page') ?? 1; + $limit = $request->get('limit') ?? self::MAX_RESULTS_PER_PAGE; + + $limit = (int) $limit; + + if ($limit <= 0) { + $limit = 1; + } + + if ($limit > self::MAX_RESULTS_PER_PAGE) { + $limit = self::MAX_RESULTS_PER_PAGE; + } + + if ($page <= 0) { + $page = 1; + } + + $paginated = $results + ->paginate( + $limit, + null, + null, + $page + ); + + $items = $paginated->items(); + foreach ($items as &$item) { + unset($item['_id']); + } + + return [ + 'per_page' => $paginated->perPage(), + 'total' => $paginated->total(), + 'current_page' => $paginated->currentPage(), + 'last_page' => $paginated->lastPage(), + 'data' => $items + ]; + } + + /** + * @param string|null $type + * @return string|null + */ + public static function mapType(?string $type = null) : ?string + { + $type = strtolower($type); + + return self::MAP_TYPES[$type] ?? null; + } + + /** + * @param string|null $status + * @return string|null + */ + public static function mapStatus(?string $status = null) : ?string + { + $status = strtolower($status); + + return self::MAP_STATUS[$status] ?? null; + } + + /** + * @param string|null $rating + * @return string|null + */ + public static function mapRating(?string $rating = null) : ?string + { + $rating = strtolower($rating); + + return self::MAP_RATING[$rating] ?? null; + } + + /** + * @param string|null $sort + * @return string|null + */ + public static function mapSort(?string $sort = null) : ?string + { + $sort = strtolower($sort); + + return $sort === 'desc' ? 'desc' : 'asc'; + } + + /** + * @param string|null $orderBy + * @return string|null + */ + public static function mapOrderBy(?string $orderBy) : ?string + { + $orderBy = strtolower($orderBy); + + return self::ORDER_BY[$orderBy] ?? null; + } +} \ No newline at end of file diff --git a/app/Http/QueryBuilder/SearchQueryBuilderCharacter.php b/app/Http/QueryBuilder/SearchQueryBuilderCharacter.php new file mode 100644 index 0000000..12b9618 --- /dev/null +++ b/app/Http/QueryBuilder/SearchQueryBuilderCharacter.php @@ -0,0 +1,102 @@ + 'mal_id', + 'name' => 'name', + 'favorites' => 'member_favorites' + ]; + + /** + * @param Request $request + * @param Builder $results + * @return Builder + */ + public static function query(Request $request, Builder $results) : Builder + { + $query = $request->get('q'); + $orderBy = self::mapOrderBy($request->get('order_by')); + $sort = self::mapSort($request->get('sort')); + $letter = $request->get('letter'); + + if (!empty($query) && is_null($letter)) { + + $results = $results + ->where('name', 'like', "%{$query}%") + ->orWhere('name_kanji', 'like', "%{$query}%") + ->orWhere('nicknames', 'like', "%{$query}%"); +// $results = $results +// ->whereRaw([ +// '$text' => [ +// '$search' => $query +// ] +// ]); + } + + if (!is_null($letter)) { + $results = $results + ->where('name', 'like', "{$letter}%"); + } + + if (empty($query) && is_null($orderBy)) { + $results = $results + ->orderBy('mal_id'); + } + + + if (!is_null($orderBy)) { + $results = $results + ->orderBy($orderBy, $sort ?? 'asc'); + } + + return $results; + } + + /** + * @param string|null $sort + * @return string|null + */ + public static function mapSort(?string $sort = null) : ?string + { + $sort = strtolower($sort); + + return $sort === 'desc' ? 'desc' : 'asc'; + } + + /** + * @param string|null $orderBy + * @return string|null + */ + public static function mapOrderBy(?string $orderBy) : ?string + { + $orderBy = strtolower($orderBy); + + return self::ORDER_BY[$orderBy] ?? null; + } +} \ No newline at end of file diff --git a/app/Http/QueryBuilder/SearchQueryBuilderClub.php b/app/Http/QueryBuilder/SearchQueryBuilderClub.php new file mode 100644 index 0000000..932dc4b --- /dev/null +++ b/app/Http/QueryBuilder/SearchQueryBuilderClub.php @@ -0,0 +1,160 @@ + 'public', + 'private' => 'private', + 'secret' => 'secret' + ]; + + /** + * @OA\Schema( + * schema="club search query category", + * description="Club Search Query Category", + * type="string", + * enum={ + * "anime","manga","actors_and_artists","characters", + * "cities_and_neighborhoods","companies","conventions","games", + * "japan","music","other","schools" + * } + * ) + */ + const MAP_CATEGORY = [ + 'anime' => 'Anime', + 'manga' => 'Manga', + 'actors_and_artists' => 'Actors & Artists', + 'characters' => 'Characters', + 'cities_and_neighborhoods' => 'Cities & Neighborhoods', + 'companies' => 'Companies', + 'conventions' => 'Conventions', + 'games' => 'Games', + 'japan' => 'Japan', + 'music' => 'Music', + 'other' => 'Other', + 'schools' => 'Schools' + ]; + + /** + * @OA\Schema( + * schema="club search query orderby", + * description="Club Search Query OrderBy", + * type="string", + * enum={"mal_id","title","members_count","pictures_count","created"} + * ) + */ + const ORDER_BY = [ + 'mal_id', 'title', 'members_count', 'pictures_count', 'created' + ]; + + /** + * @param Request $request + * @param Builder $results + * @return Builder + */ + public static function query(Request $request, Builder $results) : Builder + { + $requestType = HttpHelper::requestType($request); + $query = $request->get('q'); + $type = self::mapType($request->get('type')); + $category = self::mapCategory($request->get('category')); + $orderBy = $request->get('order_by'); + $sort = self::mapSort($request->get('sort')); + $letter = $request->get('letter'); + + if (!empty($query) && is_null($letter)) { + + $results = $results + ->where('title', 'like', "%{$query}%"); + } + + if (!is_null($letter)) { + $results = $results + ->where('title', 'like', "{$letter}%"); + } + + if (empty($query)) { + $results = $results + ->orderBy('mal_id'); + } + + if (!is_null($type)) { + $results = $results + ->where('type', $type); + } + + if (!is_null($category)) { + $results = $results + ->where('category', $category); + } + + if (!is_null($orderBy)) { + $results = $results + ->orderBy($orderBy, $sort ?? 'asc'); + } + + return $results; + } + + /** + * @param string|null $type + * @return string|null + */ + public static function mapType(?string $type = null) : ?string + { + $type = strtolower($type); + + if (!in_array($type, self::MAP_TYPES)) { + return null; + } + + return $type; + } + + /** + * @param string|null $category + * @return string|null + */ + public static function mapCategory(?string $category = null) : ?string + { + $category = strtolower($category); + + return self::MAP_CATEGORY[$category] ?? null; + } + + /** + * @param string|null $sort + * @return string|null + */ + public static function mapSort(?string $sort = null) : ?string + { + $sort = strtolower($sort); + + return $sort === 'desc' ? 'desc' : 'asc'; + } +} \ No newline at end of file diff --git a/app/Http/QueryBuilder/SearchQueryBuilderGenre.php b/app/Http/QueryBuilder/SearchQueryBuilderGenre.php new file mode 100644 index 0000000..9286163 --- /dev/null +++ b/app/Http/QueryBuilder/SearchQueryBuilderGenre.php @@ -0,0 +1,65 @@ +get('q'); + $orderBy = $request->get('order_by'); + $sort = self::mapSort($request->get('sort')); + $letter = $request->get('letter'); + + + if (!empty($query) && is_null($letter)) { + + $results = $results + ->where('name', 'like', "%{$query}%"); + } + + if (!is_null($letter)) { + $results = $results + ->where('name', 'like', "{$letter}%"); + } + + if (!is_null($orderBy)) { + $results = $results + ->orderBy($orderBy, $sort ?? 'asc'); + } + + if (empty($query)) { + $results = $results + ->orderBy('mal_id'); + } + + return $results; + } + + /** + * @param string|null $sort + * @return string|null + */ + public static function mapSort(?string $sort = null) : ?string + { + if (is_null($sort)) { + return null; + } + + $sort = strtolower($sort); + + return $sort === 'desc' ? 'desc' : 'asc'; + } +} \ No newline at end of file diff --git a/app/Http/QueryBuilder/SearchQueryBuilderInterface.php b/app/Http/QueryBuilder/SearchQueryBuilderInterface.php new file mode 100644 index 0000000..5de3011 --- /dev/null +++ b/app/Http/QueryBuilder/SearchQueryBuilderInterface.php @@ -0,0 +1,12 @@ +get('q'); + $orderBy = $request->get('order_by'); + $sort = self::mapSort($request->get('sort')); + $letter = $request->get('letter'); + + + if (!empty($query) && is_null($letter)) { + + $results = $results + ->where('name', 'like', "%{$query}%"); + } + + if (!is_null($letter)) { + $results = $results + ->where('name', 'like', "{$letter}%"); + } + + if (!is_null($orderBy)) { + $results = $results + ->orderBy($orderBy, $sort ?? 'asc'); + } + + if (empty($query)) { + $results = $results + ->orderBy('mal_id'); + } + + return $results; + } + + /** + * @param string|null $sort + * @return string|null + */ + public static function mapSort(?string $sort = null) : ?string + { + if (is_null($sort)) { + return null; + } + + $sort = strtolower($sort); + + return $sort === 'desc' ? 'desc' : 'asc'; + } +} \ No newline at end of file diff --git a/app/Http/QueryBuilder/SearchQueryBuilderManga.php b/app/Http/QueryBuilder/SearchQueryBuilderManga.php new file mode 100644 index 0000000..3a84a9a --- /dev/null +++ b/app/Http/QueryBuilder/SearchQueryBuilderManga.php @@ -0,0 +1,264 @@ + 'Manga', + 'novel' => 'Novel', + 'lightnovel' => 'Light Novel', + 'oneshot' => 'One-shot', + 'doujin' => 'Doujinshi', + 'manhwa' => 'Manhwa', + 'manhua' => 'Manhua' + ]; + + /** + * @OA\Schema( + * schema="manga search query status", + * description="Available Manga statuses", + * type="string", + * enum={"publishing","complete","hiatus","discontinued","upcoming"} + * ) + */ + const MAP_STATUS = [ + 'publishing' => 'Publishing', + 'complete' => 'Finished', + 'hiatus' => 'On Hiatus', + 'discontinued' => 'Discontinued', + 'upcoming' => 'Not yet published' + ]; + + /** + * @OA\Schema( + * schema="manga search query orderby", + * description="Available Manga order_by properties", + * type="string", + * enum={"mal_id", "title", "start_date", "end_date", "chapters", "volumes", "score", "scored_by", "rank", "popularity", "members", "favorites"} + * ) + */ + + const ORDER_BY = [ + 'mal_id' => 'mal_id', + 'title' => 'title', + 'start_date' => 'published.from', + 'end_date' => 'published.to', + 'chapters' => 'chapters', + 'volumes' => 'volumes', + 'score' => 'score', + 'scored_by' => 'scored_by', + 'rank' => 'rank', + 'popularity' => 'popularity', + 'members' => 'members', + 'favorites' => 'favorites' + ]; + + public static function query(Request $request, Builder $results) : Builder + { + $requestType = HttpHelper::requestType($request); + $query = $request->get('q'); + $type = self::mapType($request->get('type')); + $score = $request->get('score') ?? 0; + $status = self::mapStatus($request->get('status')); + $sfw = $request->get('sfw'); + $genres = $request->get('genres'); + $orderBy = self::mapOrderBy($request->get('order_by')); + $sort = self::mapSort($request->get('sort')); + $letter = $request->get('letter'); + $magazine = $request->get('magazines'); + $minScore = $request->get('min_score'); + $maxScore = $request->get('max_score'); + + if (!empty($query) && is_null($letter)) { + + $results = $results + ->where('title', 'like', "%{$query}%") + ->orWhere('title_english', 'like', "%{$query}%") + ->orWhere('title_japanese', 'like', "%{$query}%") + ->orWhere('title_synonyms', 'like', "%{$query}%"); +// $results = $results +// ->whereRaw([ +// '$text' => [ +// '$search' => $query +// ] +// ]); + } + + if (!is_null($letter)) { + $results = $results + ->where('title', 'like', "{$letter}%"); + } + + if (empty($query) && is_null($orderBy)) { + $results = $results + ->orderBy('mal_id'); + } + + if (!is_null($type)) { + $results = $results + ->where('type', $type); + } + + if ($score !== 0) { + $score = (float) $score; + + $results = $results + ->where('score', '>=', $score); + } + + if ($minScore !== null) { + $minScore = (float) $minScore; + + $results = $results + ->where('score', '>=', $minScore); + } + + if ($maxScore !== null) { + $maxScore = (float) $maxScore; + + $results = $results + ->where('score', '<=', $maxScore); + } + + if (!is_null($status)) { + $results = $results + ->where('status', $status); + } + + if (!is_null($sfw)) { + $results = $results + ->where('type', '!=', 'Doujinshi'); + } + + if (!is_null($magazine)) { + + $magazine = (int) $magazine; + + $results = $results + ->where('serializations.mal_id', $magazine); + } + + if (!is_null($genres)) { + $genres = explode(',', $genres); + + foreach ($genres as $genre) { + if (empty($genre)) { + continue; + } + + $genre = (int) $genre; + + $results = $results + ->where('genres.mal_id', $genre); + } + } + + if (!is_null($orderBy)) { + $results = $results + ->orderBy($orderBy, $sort ?? 'asc'); + } + + return $results; + } + + public static function paginate(Request $request, Builder $results) + { + $page = $request->get('page') ?? 1; + $limit = $request->get('limit') ?? self::MAX_RESULTS_PER_PAGE; + + $limit = (int) $limit; + + if ($limit <= 0) { + $limit = 1; + } + + if ($limit > self::MAX_RESULTS_PER_PAGE) { + $limit = self::MAX_RESULTS_PER_PAGE; + } + + if ($page <= 0) { + $page = 1; + } + + $paginated = $results + ->paginate( + $limit, + null, + null, + $page + ); + + $items = $paginated->items(); + foreach ($items as &$item) { + unset($item['_id']); + } + + return [ + 'per_page' => $paginated->perPage(), + 'total' => $paginated->total(), + 'current_page' => $paginated->currentPage(), + 'last_page' => $paginated->lastPage(), + 'data' => $items + ]; + } + + /** + * @param string|null $type + * @return string|null + */ + public static function mapType(?string $type = null) : ?string + { + $type = strtolower($type); + + return self::MAP_TYPES[$type] ?? null; + } + + /** + * @param string|null $status + * @return string|null + */ + public static function mapStatus(?string $status = null) : ?string + { + $status = strtolower($status); + + return self::MAP_STATUS[$status] ?? null; + } + + /** + * @param string|null $sort + * @return string|null + */ + public static function mapSort(?string $sort = null) : ?string + { + $sort = strtolower($sort); + + return $sort === 'desc' ? 'desc' : 'asc'; + } + /** + * @param string|null $orderBy + * @return string|null + */ + public static function mapOrderBy(?string $orderBy) : ?string + { + $orderBy = strtolower($orderBy); + + return self::ORDER_BY[$orderBy] ?? null; + } +} \ No newline at end of file diff --git a/app/Http/QueryBuilder/SearchQueryBuilderPeople.php b/app/Http/QueryBuilder/SearchQueryBuilderPeople.php new file mode 100644 index 0000000..c7b7b56 --- /dev/null +++ b/app/Http/QueryBuilder/SearchQueryBuilderPeople.php @@ -0,0 +1,103 @@ + 'mal_id', + 'name' => 'name', + 'birthday' => 'birthday', + 'favorites' => 'member_favorites' + ]; + + /** + * @param Request $request + * @param Builder $results + * @return Builder + */ + public static function query(Request $request, Builder $results) : Builder + { + $query = $request->get('q'); + $orderBy = self::mapOrderBy($request->get('order_by')); + $sort = self::mapSort($request->get('sort')); + $letter = $request->get('letter'); + + if (!empty($query) && is_null($letter)) { + + $results = $results + ->where('name', 'like', "%{$query}%") + ->orWhere('given_name', 'like', "%{$query}%") + ->orWhere('family_name', 'like', "%{$query}%") + ->orWhere('alternate_names', 'like', "%{$query}%"); +// $results = $results +// ->whereRaw([ +// '$text' => [ +// '$search' => $query +// ] +// ]); + } + + if (!is_null($letter)) { + $results = $results + ->where('name', 'like', "{$letter}%"); + } + + if (empty($query) && is_null($orderBy)) { + $results = $results + ->orderBy('mal_id'); + } + + if (!is_null($orderBy)) { + $results = $results + ->orderBy($orderBy, $sort ?? 'asc'); + } + + return $results; + } + + /** + * @param string|null $sort + * @return string|null + */ + public static function mapSort(?string $sort = null) : ?string + { + $sort = strtolower($sort); + + return $sort === 'desc' ? 'desc' : 'asc'; + } + + /** + * @param string|null $orderBy + * @return string|null + */ + public static function mapOrderBy(?string $orderBy) : ?string + { + $orderBy = strtolower($orderBy); + + return self::ORDER_BY[$orderBy] ?? null; + } +} \ No newline at end of file diff --git a/app/Http/QueryBuilder/SearchQueryBuilderProducer.php b/app/Http/QueryBuilder/SearchQueryBuilderProducer.php new file mode 100644 index 0000000..8b092a1 --- /dev/null +++ b/app/Http/QueryBuilder/SearchQueryBuilderProducer.php @@ -0,0 +1,75 @@ +get('q'); + $orderBy = $request->get('order_by'); + $sort = self::mapSort($request->get('sort')); + $letter = $request->get('letter'); + + + if (!empty($query) && is_null($letter)) { + + $results = $results + ->where('name', 'like', "%{$query}%"); + } + + if (!is_null($letter)) { + $results = $results + ->where('name', 'like', "{$letter}%"); + } + + if (!is_null($orderBy)) { + $results = $results + ->orderBy($orderBy, $sort ?? 'asc'); + } + + if (empty($query)) { + $results = $results + ->orderBy('mal_id'); + } + + return $results; + } + + /** + * @param string|null $sort + * @return string|null + */ + public static function mapSort(?string $sort = null) : ?string + { + if (is_null($sort)) { + return null; + } + + $sort = strtolower($sort); + + return $sort === 'desc' ? 'desc' : 'asc'; + } +} \ No newline at end of file diff --git a/app/Http/QueryBuilder/SearchQueryBuilderUsers.php b/app/Http/QueryBuilder/SearchQueryBuilderUsers.php new file mode 100644 index 0000000..8dfb13b --- /dev/null +++ b/app/Http/QueryBuilder/SearchQueryBuilderUsers.php @@ -0,0 +1,104 @@ + JikanConstants::SEARCH_USER_GENDER_ANY, + 'male' => JikanConstants::SEARCH_USER_GENDER_MALE, + 'female' => JikanConstants::SEARCH_USER_GENDER_FEMALE, + 'nonbinary' => JikanConstants::SEARCH_USER_GENDER_NONBINARY + ]; + + + public static function query(Request $request) + { + $page = $request->get('page') ?? 1; + $query = $request->get('q'); + $gender = self::mapGender($request->get('gender')); + $location = $request->get('location'); + $maxAge = $request->get('maxAge'); + $minAge = $request->get('minAge'); + + return (new UserSearchRequest()) + ->setQuery($query) + ->setGender($gender) + ->setLocation($location) + ->setMaxAge($maxAge) + ->setMinAge($minAge) + ->setPage($page); + + } + + public static function paginate(Request $request, Builder $results) + { + $page = $request->get('page') ?? 1; + $limit = $request->get('limit') ?? self::MAX_RESULTS_PER_PAGE; + + $limit = (int) $limit; + + if ($limit <= 0) { + $limit = 1; + } + + if ($limit > self::MAX_RESULTS_PER_PAGE) { + $limit = self::MAX_RESULTS_PER_PAGE; + } + + if ($page <= 0) { + $page = 1; + } + + $paginated = $results + ->paginate( + $limit, + null, + null, + $page + ); + + $items = $paginated->items(); + foreach ($items as &$item) { + unset($item['_id']); + } + + return [ + 'per_page' => $paginated->perPage(), + 'total' => $paginated->total(), + 'current_page' => $paginated->currentPage(), + 'last_page' => $paginated->lastPage(), + 'data' => $items + ]; + } + + public static function mapGender(?string $type = null) : ?int + { + if (!is_null($type)) { + return null; + } + + $type = strtolower($type); + + return self::MAP_GENDERS[$type] ?? null; + } + +} \ No newline at end of file diff --git a/app/Http/QueryBuilder/TopQueryBuilderAnime.php b/app/Http/QueryBuilder/TopQueryBuilderAnime.php new file mode 100644 index 0000000..eccee4d --- /dev/null +++ b/app/Http/QueryBuilder/TopQueryBuilderAnime.php @@ -0,0 +1,117 @@ + 'TV', + 'movie' => 'Movie', + 'ova' => 'OVA', + 'special' => 'Special', + 'ona' => 'ONA', + 'music' => 'Music', + ]; + + /** + * + */ + const MAP_FILTER = [ + 'airing', 'upcoming', 'bypopularity', 'favorites' + ]; + + + /** + * @param string|null $type + * @return string|null + */ + public static function mapType(?string $type = null) : ?string + { + if (is_null($type)) { + return null; + } + + $type = strtolower($type); + + return self::MAP_TYPES[$type] ?? null; + } + + /** + * @param Request $request + * @param Builder $builder + * @return Builder + */ + public static function query(Request $request, Builder $results) : Builder + { + $animeType = self::mapType($request->get('type')); + $filterType = self::mapFilter($request->get('filter')); + + $results = $results + ->whereNotNull('rank') + ->where('rank', '>', 0) + ->orderBy('rank', 'asc') + ->where('status', '!=', 'Not yet aired') + ->where('rating', '!=', 'Rx - Hentai'); + + if (!is_null($animeType)) { + $results = $results + ->where('type', $animeType); + } + + if (!is_null($filterType) && $filterType === 'airing') { + $results = $results + ->where('airing', true); + } + + if (!is_null($filterType) && $filterType === 'upcoming') { + $results = $results + ->where('status', 'Not yet aired'); + } + + if (!is_null($filterType) && $filterType === 'bypopularity') { + $results = $results + ->orderBy('members', 'desc'); + } + + if (!is_null($filterType) && $filterType === 'favorite') { + $results = $results + ->orderBy('favorites', 'desc'); + } + + return $results; + } + + + /** + * @param string|null $filter + * @return string|null + */ + public static function mapFilter(?string $filter = null) : ?string + { + $filter = strtolower($filter); + + if (!\in_array($filter, self::MAP_FILTER)) { + return null; + } + + return $filter; + } +} \ No newline at end of file diff --git a/app/Http/QueryBuilder/TopQueryBuilderManga.php b/app/Http/QueryBuilder/TopQueryBuilderManga.php new file mode 100644 index 0000000..2717692 --- /dev/null +++ b/app/Http/QueryBuilder/TopQueryBuilderManga.php @@ -0,0 +1,109 @@ + 'Manga', + 'novels' => 'Novel', + 'oneshots' => 'One-shot', + 'doujin' => 'Doujinshi', + 'manhwa' => 'Manhwa', + 'manhua' => 'Manhua' + ]; + + /** + * + */ + const MAP_FILTER = [ + 'upcoming', 'bypopularity', 'favorites' + ]; + + /** + * @param Request $request + * @param Builder $builder + * @return Builder + */ + public static function query(Request $request, Builder $results) : Builder + { + $mangaType = self::mapType($request->get('type')); + $filterType = self::mapFilter($request->get('filter')); + + $results = $results + ->whereNotNull('rank') + ->where('rank', '>', 0) + ->orderBy('rank', 'asc') + ->where('type', '!=', 'Doujinshi'); + + if (!is_null($mangaType)) { + $results = $results + ->where('type', $mangaType); + } + + if (!is_null($filterType) && $filterType === 'publishing') { + $results = $results + ->where('publishing', true); + } + + if (!is_null($filterType) && $filterType === 'bypopularity') { + $results = $results + ->orderBy('popularity', 'desc'); + } + + if (!is_null($filterType) && $filterType === 'favorite') { + $results = $results + ->orderBy('favorites', 'desc'); + } + + return $results; + } + + /** + * @param string|null $type + * @return string|null + */ + public static function mapType(?string $type = null) : ?string + { + if (is_null($type)) { + return null; + } + + $type = strtolower($type); + + return self::MAP_TYPES[$type] ?? null; + } + + /** + * @param string|null $filter + * @return string|null + */ + public static function mapFilter(?string $filter = null) : ?string + { + $filter = strtolower($filter); + + if (!\in_array($filter, self::MAP_FILTER)) { + return null; + } + + return $filter; + } + } \ No newline at end of file diff --git a/app/Providers/UserListQueryBuilder.php b/app/Http/QueryBuilder/UserListQueryBuilder.php similarity index 50% rename from app/Providers/UserListQueryBuilder.php rename to app/Http/QueryBuilder/UserListQueryBuilder.php index f73b010..583f341 100755 --- a/app/Providers/UserListQueryBuilder.php +++ b/app/Http/QueryBuilder/UserListQueryBuilder.php @@ -1,10 +1,10 @@ JikanConstants::USER_MANGA_LIST_NOT_YET_PUBLISHED, ]; - public static function create(Request $request, $parser) + public static function create(Request $request, $parserRequest) { - $query = $request->get('search') ?? null; - $search = $request->get('q') ?? null; - $page = $request->get('page') ?? null; - $sort = $request->get('sort') ?? null; - $orderBy = $request->get('order_by') ?? null; - $orderBy2 = $request->get('order_by2') ?? null; + $query = $request->get('q'); + $sort = $request->get('sort'); + $orderBy = $request->get('order_by'); + $orderBy2 = $request->get('order_by2'); + $airedFrom = $request->get('aired_from'); + $airedTo = $request->get('aired_to'); + $producer = $request->get('producer'); + $magazine = $request->get('magazine'); + $season = $request->get('season'); + $year = $request->get('year'); + $airingStatus = $request->get('airing_status'); + $publishedFrom = $request->get('published_from'); + $publishedTo = $request->get('published_to'); + $publishingStatus = $request->get('publishing_status'); - // anime only - $airedFrom = $request->get('aired_from') ?? null; - $airedTo = $request->get('aired_to') ?? null; - $producer = $request->get('producer') ?? null; - $season = $request->get('season') ?? null; - $year = $request->get('year') ?? null; - $airingStatus = $request->get('airing_status') ?? null; - - // manga only - $publishedFrom = $request->get('published_from') ?? null; - $publishedTo = $request->get('published_to') ?? null; - $magazine = $request->get('magazine') ?? null; - $publishingStatus = $request->get('publishing_status') ?? null; - - - // search - if ($search !== null) { - $parser->setTitle($search); - } - // bc: alias - if ($query !== null) { - $parser->setTitle($query); + // Search + if (!is_null($query)) { + $parserRequest->setTitle($query); } - // page - if ($page !== null) { - $parser->setPage((int) $page); + // Page + $parserRequest->setPage( + (int) $request->get('page') ?? 1 + ); + + // Sort + $sort = $request->get('sort'); + if (!is_null($sort)) { + + if (array_key_exists($sort, self::VALID_SORT)) { + $sort = self::VALID_SORT[$sort]; + } } - // sort - if ($sort !== null && array_key_exists($sort, self::VALID_SORT)) { - $sort = self::VALID_SORT[$sort]; - } + if ($parserRequest instanceof UserAnimeListRequest) { + // Order By + if (!is_null($orderBy)) { - // animelist only queries - if ($parser instanceof UserAnimeListRequest) { + if (array_key_exists($orderBy, self::VALID_ANIME_ORDER_BY)) { + $orderBy = self::VALID_ANIME_ORDER_BY[$orderBy]; - // order by - if ($orderBy !== null && array_key_exists($orderBy, self::VALID_ANIME_ORDER_BY)) { - $orderBy = self::VALID_ANIME_ORDER_BY[$orderBy]; - - $parser->setOrderBy($orderBy, $sort); + $parserRequest->setOrderBy($orderBy, $sort); + } } - // order by 2 - if ($orderBy2 !== null && array_key_exists($orderBy2, self::VALID_ANIME_ORDER_BY)) { - $orderBy2 = self::VALID_ANIME_ORDER_BY[$orderBy2]; + // Order By 2 + if (!is_null($orderBy2)) { - $parser->setOrderBy2($orderBy2, $sort); + if (array_key_exists($orderBy2, self::VALID_ANIME_ORDER_BY)) { + $orderBy2 = self::VALID_ANIME_ORDER_BY[$orderBy2]; + + $parserRequest->setOrderBy2($orderBy2, $sort); + } } - // aired from - if ($airedFrom !== null && preg_match("~[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}~", $airedFrom)) { - $airedFrom = explode("-", $airedFrom); + // Aired From + if (!is_null($airedFrom)) { - $parser->setAiredFrom( - (int) $airedFrom[0], - (int) $airedFrom[1], - (int) $airedFrom[2] + if (preg_match("~[0-9]{4}-[0-9]{2}-[0-9]{2}~", $airedFrom)) { + $airedFrom = explode("-", $airedFrom); + $parserRequest->setAiredFrom( + (int) $airedFrom[2], + (int) $airedFrom[1], + (int) $airedFrom[0] + ); + } + } + + // Aired To + if (!is_null($airedTo)) { + + if (preg_match("~[0-9]{4}-[0-9]{2}-[0-9]{2}~", $airedTo)) { + $airedTo = explode("-", $airedTo); + $parserRequest->setAiredTo( + (int) $airedTo[2], + (int) $airedTo[1], + (int) $airedTo[0] + ); + } + } + + // Producer + if (!is_null($producer)) { + + $parserRequest->setProducer( + (int) $producer ); } - // aired to - if ($airedTo !== null) { - if (preg_match("~[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}~", $airedTo)) { - $airedTo = explode("-", $airedTo); + // Season + if (!is_null($season)) { - $parser->setAiredTo( - (int) $airedTo[0], - (int) $airedTo[1], - (int) $airedTo[2] - ); + if (\in_array($season, self::VALID_SEASONS)) { + $parserRequest->setSeason($season); } } - // producer - if ($producer !== null) { - $parser->setProducer((int) $producer); + // Year + if (!is_null($year)) { + + $parserRequest->setSeasonYear( + (int) $year + ); } - // season - if ($season !== null && in_array($season, self::VALID_SEASONS)) { - $parser->setSeason($season); + // Airing Status + if (!is_null($airingStatus)) { + + if (array_key_exists($airingStatus, self::VALID_AIRING_STATUS)) { + $airingStatus = self::VALID_AIRING_STATUS[$airingStatus]; + $parserRequest->setAiringStatus($airingStatus); + } } - - // year - if ($year !== null) { - $parser->setSeasonYear($year); - } - - // airing status - if ($airingStatus !== null && array_key_exists($airingStatus, self::VALID_AIRING_STATUS)) { - $airingStatus = self::VALID_AIRING_STATUS[$airingStatus]; - - $parser->setAiringStatus($airingStatus); - } - } - if ($parser instanceof UserMangaListRequest) { - // order by - if ($orderBy !== null && array_key_exists($orderBy, self::VALID_MANGA_ORDER_BY)) { - $orderBy = self::VALID_MANGA_ORDER_BY[$orderBy]; + if ($parserRequest instanceof UserMangaListRequest) { + // Order By + if (!is_null($orderBy)) { - $parser->setOrderBy($orderBy, $sort); + if (array_key_exists($orderBy, self::VALID_MANGA_ORDER_BY)) { + $orderBy = self::VALID_MANGA_ORDER_BY[$orderBy]; + + $parserRequest->setOrderBy($orderBy, $sort); + } } - // order by 2 - if ($orderBy2 !== null && array_key_exists($orderBy2, self::VALID_MANGA_ORDER_BY)) { - $orderBy2 = self::VALID_MANGA_ORDER_BY[$orderBy2]; + // Order By 2 + if (!is_null($orderBy2)) { - $parser->setOrderBy2($orderBy2, $sort); + if (array_key_exists($orderBy2, self::VALID_MANGA_ORDER_BY)) { + $orderBy2 = self::VALID_MANGA_ORDER_BY[$orderBy2]; + + $parserRequest->setOrderBy2($orderBy2, $sort); + } } - // published from - if ($publishedFrom !== null) { - if (preg_match("~[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}~", $publishedFrom)) { + // Published From + if (!is_null($publishedFrom)) { + + if (preg_match("~[0-9]{4}-[0-9]{2}-[0-9]{2}~", $publishedFrom)) { $publishedFrom = explode("-", $publishedFrom); - - $parser->setPublishedFrom( - (int) $publishedFrom[0], + $parserRequest->setPublishedFrom( + (int) $publishedFrom[2], (int) $publishedFrom[1], - (int) $publishedFrom[2] + (int) $publishedFrom[0] ); } } - // published to - if ($publishedTo !== null) { - if (preg_match("~[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}~", $publishedTo)) { + // Published To + if (!is_null($publishedTo)) { + + if (preg_match("~[0-9]{4}-[0-9]{2}-[0-9]{2}~", $publishedTo)) { $publishedTo = explode("-", $publishedTo); - - $parser->setPublishedTo( - (int) $publishedTo[0], + $parserRequest->setPublishedTo( + (int) $publishedTo[2], (int) $publishedTo[1], - (int) $publishedTo[2] + (int) $publishedTo[0] ); } } - - // magazine - if ($magazine !== null) { - $parser->setMagazine((int) $magazine); + // Magazine + if (!is_null($magazine)) { + $parserRequest->setMagazine( + (int) $magazine + ); } - // airing status - if ($publishingStatus !== null && array_key_exists($publishingStatus, self::VALID_PUBLISHING_STATUS)) { - $publishingStatus = self::VALID_PUBLISHING_STATUS[$publishingStatus]; + // Publishing Status + if (!is_null($publishingStatus)) { - $parser->setPublishingStatus($publishingStatus); + if (array_key_exists($publishingStatus, self::VALID_PUBLISHING_STATUS)) { + $publishingStatus = self::VALID_PUBLISHING_STATUS[$publishingStatus]; + $parserRequest->setPublishingStatus($publishingStatus); + } } - } - return $parser; + return $parserRequest; } } diff --git a/app/Http/Resources/V4/AnimeCharactersResource.php b/app/Http/Resources/V4/AnimeCharactersResource.php new file mode 100644 index 0000000..a01698a --- /dev/null +++ b/app/Http/Resources/V4/AnimeCharactersResource.php @@ -0,0 +1,102 @@ +pagination = [ + 'last_visible_page' => $resource->lastPage(), + 'has_next_page' => $resource->hasMorePages() + ]; + + $this->collection = $resource->getCollection(); + + parent::__construct($resource); + } + + /** + * Transform the resource collection into an array. + * + * @param \Illuminate\Http\Request $request + * @return array + */ + public function toArray($request) + { + return [ + 'pagination' => $this->pagination, + 'data' => $this->collection + ]; + } + + public function withResponse($request, $response) + { + $jsonResponse = json_decode($response->getContent(), true); + unset($jsonResponse['links'],$jsonResponse['meta']); + $response->setContent(json_encode($jsonResponse)); + } +} \ No newline at end of file diff --git a/app/Http/Resources/V4/AnimeEpisodeResource.php b/app/Http/Resources/V4/AnimeEpisodeResource.php new file mode 100644 index 0000000..d386e91 --- /dev/null +++ b/app/Http/Resources/V4/AnimeEpisodeResource.php @@ -0,0 +1,92 @@ + $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..7ca4269 --- /dev/null +++ b/app/Http/Resources/V4/AnimeEpisodesResource.php @@ -0,0 +1,23 @@ + $this['last_visible_page'] ?? 1, + 'has_next_page' => $this['has_next_page'] ?? false, + 'results' => $this['results'] + ]; + } +} \ No newline at end of file diff --git a/app/Http/Resources/V4/AnimeRelationsResource.php b/app/Http/Resources/V4/AnimeRelationsResource.php new file mode 100644 index 0000000..8694bd8 --- /dev/null +++ b/app/Http/Resources/V4/AnimeRelationsResource.php @@ -0,0 +1,49 @@ + $this->mal_id, + 'url' => $this->url, + 'images' => $this->images, + 'trailer' => $this->trailer, + 'title' => $this->title, + 'title_english' => $this->title_english, + 'title_japanese' => $this->title_japanese, + 'title_synonyms' => $this->title_synonyms, + 'type' => $this->type, + 'source' => $this->source, + 'episodes' => $this->episodes, + 'status' => $this->status, + 'airing' => $this->airing, + 'aired' => $this->aired, + 'duration' => $this->duration, + 'rating' => $this->rating, + 'score' => $this->score, + 'scored_by' => $this->scored_by, + 'rank' => $this->rank, + 'popularity' => $this->popularity, + 'members' => $this->members, + 'favorites' => $this->favorites, + 'synopsis' => $this->synopsis, + 'background' => $this->background, + 'season' => $this->season, + 'year' => $this->year, + 'broadcast' => $this->broadcast, + 'producers' => $this->producers, + 'licensors' => $this->licensors, + 'studios' => $this->studios, + 'genres' => $this->genres, + 'explicit_genres' => $this->explicit_genres, + 'themes' => $this->themes, + 'demographics' => $this->demographics, + ]; + } +} \ No newline at end of file diff --git a/app/Http/Resources/V4/AnimeStaffResource.php b/app/Http/Resources/V4/AnimeStaffResource.php new file mode 100644 index 0000000..1f86db8 --- /dev/null +++ b/app/Http/Resources/V4/AnimeStaffResource.php @@ -0,0 +1,69 @@ + $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/AnimeThemesResource.php b/app/Http/Resources/V4/AnimeThemesResource.php new file mode 100644 index 0000000..615377a --- /dev/null +++ b/app/Http/Resources/V4/AnimeThemesResource.php @@ -0,0 +1,49 @@ + $this->opening_themes, + 'endings' => $this->ending_themes + ]; + } +} \ No newline at end of file diff --git a/app/Http/Resources/V4/AnimeVideosResource.php b/app/Http/Resources/V4/AnimeVideosResource.php new file mode 100644 index 0000000..8781418 --- /dev/null +++ b/app/Http/Resources/V4/AnimeVideosResource.php @@ -0,0 +1,83 @@ + $this['promo'], + 'episodes' => $this['episodes'] + ]; + } +} \ No newline at end of file diff --git a/app/Http/Resources/V4/CharacterAnimeCollection.php b/app/Http/Resources/V4/CharacterAnimeCollection.php new file mode 100644 index 0000000..fb34a12 --- /dev/null +++ b/app/Http/Resources/V4/CharacterAnimeCollection.php @@ -0,0 +1,53 @@ + $this->collection + ]; + } +} \ No newline at end of file diff --git a/app/Http/Resources/V4/CharacterAnimeResource.php b/app/Http/Resources/V4/CharacterAnimeResource.php new file mode 100644 index 0000000..ab768ef --- /dev/null +++ b/app/Http/Resources/V4/CharacterAnimeResource.php @@ -0,0 +1,28 @@ + $this['role'], + 'anime' => [ + 'mal_id' => $this['anime']['mal_id'], + 'url' => $this['anime']['url'], + 'images' => $this['anime']['images'], + 'title' => $this['anime']['title'] + ], + ]; + } +} \ No newline at end of file diff --git a/app/Http/Resources/V4/CharacterCollection.php b/app/Http/Resources/V4/CharacterCollection.php new file mode 100644 index 0000000..048b343 --- /dev/null +++ b/app/Http/Resources/V4/CharacterCollection.php @@ -0,0 +1,72 @@ +pagination = [ + 'last_visible_page' => $resource->lastPage(), + 'has_next_page' => $resource->hasMorePages() + ]; + + $this->collection = $resource->getCollection(); + + parent::__construct($resource); + } + + /** + * Transform the resource collection into an array. + * + * @param \Illuminate\Http\Request $request + * @return array + */ + public function toArray($request) + { + return [ + 'pagination' => $this->pagination, + 'data' => $this->collection + ]; + } + + public function withResponse($request, $response) + { + $jsonResponse = json_decode($response->getContent(), true); + unset($jsonResponse['links'],$jsonResponse['meta']); + $response->setContent(json_encode($jsonResponse)); + } +} \ No newline at end of file diff --git a/app/Http/Resources/V4/CharacterMangaCollection.php b/app/Http/Resources/V4/CharacterMangaCollection.php new file mode 100644 index 0000000..aaa6c5c --- /dev/null +++ b/app/Http/Resources/V4/CharacterMangaCollection.php @@ -0,0 +1,53 @@ + $this->collection + ]; + } +} \ No newline at end of file diff --git a/app/Http/Resources/V4/CharacterMangaResource.php b/app/Http/Resources/V4/CharacterMangaResource.php new file mode 100644 index 0000000..58e5e3c --- /dev/null +++ b/app/Http/Resources/V4/CharacterMangaResource.php @@ -0,0 +1,28 @@ + $this['role'], + 'manga' => [ + 'mal_id' => $this['manga']['mal_id'], + 'url' => $this['manga']['url'], + 'images' => $this['manga']['images'], + 'title' => $this['manga']['title'] + ], + ]; + } +} \ No newline at end of file diff --git a/app/Http/Resources/V4/CharacterResource.php b/app/Http/Resources/V4/CharacterResource.php new file mode 100644 index 0000000..30c350a --- /dev/null +++ b/app/Http/Resources/V4/CharacterResource.php @@ -0,0 +1,133 @@ + $this->mal_id, + 'url' => $this->url, + 'images' => $this->images, + 'name' => $this->name, + 'nicknames' => $this->nicknames, + 'favorites' => $this->favorites, + 'about' => $this->about + ]; + } +} \ No newline at end of file diff --git a/app/Http/Resources/V4/CharacterSeiyuuCollection.php b/app/Http/Resources/V4/CharacterSeiyuuCollection.php new file mode 100644 index 0000000..5ddbf8e --- /dev/null +++ b/app/Http/Resources/V4/CharacterSeiyuuCollection.php @@ -0,0 +1,53 @@ + $this->collection + ]; + } +} \ No newline at end of file diff --git a/app/Http/Resources/V4/CharacterSeiyuuResource.php b/app/Http/Resources/V4/CharacterSeiyuuResource.php new file mode 100644 index 0000000..7c41f2a --- /dev/null +++ b/app/Http/Resources/V4/CharacterSeiyuuResource.php @@ -0,0 +1,28 @@ + $this['language'], + 'person' => [ + 'mal_id' => $this['person']['mal_id'], + 'url' => $this['person']['url'], + 'images' => $this['person']['images'], + 'name' => $this['person']['name'] + ], + ]; + } +} \ No newline at end of file diff --git a/app/Http/Resources/V4/ClubCollection.php b/app/Http/Resources/V4/ClubCollection.php new file mode 100644 index 0000000..789f1dc --- /dev/null +++ b/app/Http/Resources/V4/ClubCollection.php @@ -0,0 +1,79 @@ +pagination = [ + 'last_visible_page' => $resource->lastPage(), + 'has_next_page' => $resource->hasMorePages() + ]; + + $this->collection = $resource->getCollection(); + + parent::__construct($resource); + } + + /** + * Transform the resource collection into an array. + * + * @param \Illuminate\Http\Request $request + * @return array + */ + public function toArray($request) + { + return [ + 'pagination' => $this->pagination, + 'data' => $this->collection + ]; + } + + public function withResponse($request, $response) + { + $jsonResponse = json_decode($response->getContent(), true); + unset($jsonResponse['links'],$jsonResponse['meta']); + $response->setContent(json_encode($jsonResponse)); + } +} \ No newline at end of file diff --git a/app/Http/Resources/V4/ClubRelationsResource.php b/app/Http/Resources/V4/ClubRelationsResource.php new file mode 100644 index 0000000..a1a76ea --- /dev/null +++ b/app/Http/Resources/V4/ClubRelationsResource.php @@ -0,0 +1,69 @@ + $this['anime'], + 'manga' => $this['manga'], + 'characters' => $this['characters'], + ]; + } +} \ No newline at end of file diff --git a/app/Http/Resources/V4/ClubResource.php b/app/Http/Resources/V4/ClubResource.php new file mode 100644 index 0000000..2cb3267 --- /dev/null +++ b/app/Http/Resources/V4/ClubResource.php @@ -0,0 +1,81 @@ + $this->mal_id, + 'url' => $this->url, + 'images' => $this->images, + 'name' => $this->name, + 'members' => $this->members, + 'category' => $this->category, + 'created' => $this->created, + 'access' => $this->access, + ]; + } +} \ No newline at end of file diff --git a/app/Http/Resources/V4/ClubStaffResource.php b/app/Http/Resources/V4/ClubStaffResource.php new file mode 100644 index 0000000..08c22b3 --- /dev/null +++ b/app/Http/Resources/V4/ClubStaffResource.php @@ -0,0 +1,47 @@ +collection; + } +} \ No newline at end of file diff --git a/app/Http/Resources/V4/GenreResource.php b/app/Http/Resources/V4/GenreResource.php new file mode 100644 index 0000000..c70b3df --- /dev/null +++ b/app/Http/Resources/V4/GenreResource.php @@ -0,0 +1,49 @@ + $this['mal_id'], + 'name' => $this['name'], + 'url' => $this['url'], + 'count' => $this['count'] + ]; + } +} \ No newline at end of file diff --git a/app/Http/Resources/V4/InsightsCollection.php b/app/Http/Resources/V4/InsightsCollection.php new file mode 100644 index 0000000..886daba --- /dev/null +++ b/app/Http/Resources/V4/InsightsCollection.php @@ -0,0 +1,55 @@ +pagination = [ + 'last_visible_page' => $resource->lastPage(), + 'has_next_page' => $resource->hasMorePages() + ]; + + $this->collection = $resource->getCollection(); + + parent::__construct($resource); + } + + /** + * Transform the resource collection into an array. + * + * @param Request $request + * @return array + */ + public function toArray($request) + { + return [ + 'pagination' => $this->pagination, + 'data' => $this->collection + ]; + } + + public function withResponse($request, $response) + { + $jsonResponse = json_decode($response->getContent(), true); + unset($jsonResponse['links'],$jsonResponse['meta']); + $response->setContent(json_encode($jsonResponse)); + } +} \ No newline at end of file diff --git a/app/Http/Resources/V4/InsightsResource.php b/app/Http/Resources/V4/InsightsResource.php new file mode 100644 index 0000000..acd6d26 --- /dev/null +++ b/app/Http/Resources/V4/InsightsResource.php @@ -0,0 +1,17 @@ + $this['timestamp'], + 'url' => $this['url'], + ]; + } +} \ No newline at end of file diff --git a/app/Http/Resources/V4/MagazineCollection.php b/app/Http/Resources/V4/MagazineCollection.php new file mode 100644 index 0000000..6ddeb7f --- /dev/null +++ b/app/Http/Resources/V4/MagazineCollection.php @@ -0,0 +1,102 @@ +pagination = [ + 'last_visible_page' => $resource->lastPage(), + 'has_next_page' => $resource->hasMorePages() + ]; + + $this->collection = $resource->getCollection(); + + parent::__construct($resource); + } + + /** + * Transform the resource collection into an array. + * + * @param \Illuminate\Http\Request $request + * @return array + */ + public function toArray($request) + { + return [ + 'pagination' => $this->pagination, + 'data' => $this->collection + ]; + } + + public function withResponse($request, $response) + { + $jsonResponse = json_decode($response->getContent(), true); + unset($jsonResponse['links'],$jsonResponse['meta']); + $response->setContent(json_encode($jsonResponse)); + } +} \ No newline at end of file diff --git a/app/Http/Resources/V4/MagazineResource.php b/app/Http/Resources/V4/MagazineResource.php new file mode 100644 index 0000000..af9513e --- /dev/null +++ b/app/Http/Resources/V4/MagazineResource.php @@ -0,0 +1,49 @@ + $this->mal_id, + 'name' => $this->name, + 'url' => $this->url, + 'count' => $this->count + ]; + } +} \ No newline at end of file diff --git a/app/Http/Resources/V4/MangaCharactersResource.php b/app/Http/Resources/V4/MangaCharactersResource.php new file mode 100644 index 0000000..d269d0e --- /dev/null +++ b/app/Http/Resources/V4/MangaCharactersResource.php @@ -0,0 +1,43 @@ +pagination = [ + 'last_visible_page' => $resource->lastPage(), + 'has_next_page' => $resource->hasMorePages() + ]; + + $this->collection = $resource->getCollection(); + + parent::__construct($resource); + } + + /** + * Transform the resource collection into an array. + * + * @param \Illuminate\Http\Request $request + * @return array + */ + public function toArray($request) + { + return [ + 'pagination' => $this->pagination, + 'data' => $this->collection + ]; + } + + public function withResponse($request, $response) + { + $jsonResponse = json_decode($response->getContent(), true); + unset($jsonResponse['links'],$jsonResponse['meta']); + $response->setContent(json_encode($jsonResponse)); + } +} \ No newline at end of file diff --git a/app/Http/Resources/V4/MangaRelationsResource.php b/app/Http/Resources/V4/MangaRelationsResource.php new file mode 100644 index 0000000..774ab48 --- /dev/null +++ b/app/Http/Resources/V4/MangaRelationsResource.php @@ -0,0 +1,20 @@ + $this->mal_id, + 'url' => $this->url, + 'images' => $this->images, + 'title' => $this->title, + 'title_english' => $this->title_english, + 'title_japanese' => $this->title_japanese, + 'title_synonyms' => $this->title_synonyms, + 'type' => $this->type, + 'chapters' => $this->chapters, + 'volumes' => $this->volumes, + 'status' => $this->status, + 'publishing' => $this->publishing, + 'published' => $this->published, + 'scored' => $this->score, + 'scored_by' => $this->scored_by, + 'rank' => $this->rank, + 'popularity' => $this->popularity, + 'members' => $this->members, + 'favorites' => $this->favorites, + 'synopsis' => $this->synopsis, + 'background' => $this->background, + 'authors' => $this->authors, + 'serializations' => $this->serializations, + 'genres' => $this->genres, + 'explicit_genres' => $this->explicit_genres, + 'themes' => $this->themes, + 'demographics' => $this->demographics, + ]; + } +} \ No newline at end of file diff --git a/app/Http/Resources/V4/MangaStatisticsResource.php b/app/Http/Resources/V4/MangaStatisticsResource.php new file mode 100644 index 0000000..fe0bd1d --- /dev/null +++ b/app/Http/Resources/V4/MangaStatisticsResource.php @@ -0,0 +1,92 @@ + $this['reading'], + 'completed' => $this['completed'], + 'on_hold' => $this['on_hold'], + 'dropped' => $this['dropped'], + 'plan_to_read' => $this['plan_to_read'], + 'total' => $this['total'], + 'scores' => $this['scores'] + ]; + } +} \ No newline at end of file diff --git a/app/Http/Resources/V4/MoreInfoResource.php b/app/Http/Resources/V4/MoreInfoResource.php new file mode 100644 index 0000000..e79e60a --- /dev/null +++ b/app/Http/Resources/V4/MoreInfoResource.php @@ -0,0 +1,37 @@ + $this['moreinfo'] + ]; + } +} \ No newline at end of file diff --git a/app/Http/Resources/V4/NewsResource.php b/app/Http/Resources/V4/NewsResource.php new file mode 100644 index 0000000..7a6dc1e --- /dev/null +++ b/app/Http/Resources/V4/NewsResource.php @@ -0,0 +1,89 @@ + [ + 'last_visible_page' => $this['last_visible_page'] ?? 1, + 'has_next_page' => $this['has_next_page'] ?? false, + ], + 'data' => $this['results'], + ]; + } +} \ No newline at end of file diff --git a/app/Http/Resources/V4/PersonAnimeCollection.php b/app/Http/Resources/V4/PersonAnimeCollection.php new file mode 100644 index 0000000..2b8e678 --- /dev/null +++ b/app/Http/Resources/V4/PersonAnimeCollection.php @@ -0,0 +1,53 @@ + $this->collection + ]; + } +} \ No newline at end of file diff --git a/app/Http/Resources/V4/PersonAnimeResource.php b/app/Http/Resources/V4/PersonAnimeResource.php new file mode 100644 index 0000000..520dc5c --- /dev/null +++ b/app/Http/Resources/V4/PersonAnimeResource.php @@ -0,0 +1,28 @@ + $this['position'], + 'anime' => [ + 'mal_id' => $this['anime']['mal_id'], + 'url' => $this['anime']['url'], + 'images' => $this['anime']['images'], + 'title' => $this['anime']['title'] + ] + ]; + } +} \ No newline at end of file diff --git a/app/Http/Resources/V4/PersonCollection.php b/app/Http/Resources/V4/PersonCollection.php new file mode 100644 index 0000000..10739aa --- /dev/null +++ b/app/Http/Resources/V4/PersonCollection.php @@ -0,0 +1,74 @@ +pagination = [ + 'last_visible_page' => $resource->lastPage(), + 'has_next_page' => $resource->hasMorePages() + ]; + + $this->collection = $resource->getCollection(); + + parent::__construct($resource); + } + + /** + * Transform the resource collection into an array. + * + * @param \Illuminate\Http\Request $request + * @return array + */ + public function toArray($request) + { + return [ + 'pagination' => $this->pagination, + 'data' => $this->collection + ]; + } + + public function withResponse($request, $response) + { + $jsonResponse = json_decode($response->getContent(), true); + unset($jsonResponse['links'],$jsonResponse['meta']); + $response->setContent(json_encode($jsonResponse)); + } +} \ No newline at end of file diff --git a/app/Http/Resources/V4/PersonMangaCollection.php b/app/Http/Resources/V4/PersonMangaCollection.php new file mode 100644 index 0000000..2a1a6df --- /dev/null +++ b/app/Http/Resources/V4/PersonMangaCollection.php @@ -0,0 +1,53 @@ + $this->collection + ]; + } +} \ No newline at end of file diff --git a/app/Http/Resources/V4/PersonMangaResource.php b/app/Http/Resources/V4/PersonMangaResource.php new file mode 100644 index 0000000..ead9f6e --- /dev/null +++ b/app/Http/Resources/V4/PersonMangaResource.php @@ -0,0 +1,28 @@ + $this['position'], + 'manga' => [ + 'mal_id' => $this['manga']['mal_id'], + 'url' => $this['manga']['url'], + 'images' => $this['manga']['images'], + 'title' => $this['manga']['title'] + ], + ]; + } +} \ No newline at end of file diff --git a/app/Http/Resources/V4/PersonResource.php b/app/Http/Resources/V4/PersonResource.php new file mode 100644 index 0000000..6a68d4c --- /dev/null +++ b/app/Http/Resources/V4/PersonResource.php @@ -0,0 +1,98 @@ + $this->mal_id, + 'url' => $this->url, + 'website_url' => $this->website_url, + 'images' => $this->images, + 'name' => $this->name, + 'given_name' => $this->given_name, + 'family_name' => $this->family_name, + 'alternate_names' => $this->alternate_names, + 'birthday' => $this->birthday, + 'favorites' => $this->favorites, + 'about' => $this->about + ]; + } +} \ No newline at end of file diff --git a/app/Http/Resources/V4/PersonVoiceResource.php b/app/Http/Resources/V4/PersonVoiceResource.php new file mode 100644 index 0000000..2bf63a8 --- /dev/null +++ b/app/Http/Resources/V4/PersonVoiceResource.php @@ -0,0 +1,34 @@ + $this['role'], + 'anime' => [ + 'mal_id' => $this['anime']['mal_id'], + 'url' => $this['anime']['url'], + 'images' => $this['anime']['images'], + 'title' => $this['anime']['title'] + ], + 'character' => [ + 'mal_id' => $this['character']['mal_id'], + 'url' => $this['character']['url'], + 'images' => $this['character']['images'], + 'name' => $this['character']['name'] + ] + ]; + } +} \ No newline at end of file diff --git a/app/Http/Resources/V4/PersonVoicesCollection.php b/app/Http/Resources/V4/PersonVoicesCollection.php new file mode 100644 index 0000000..0575fad --- /dev/null +++ b/app/Http/Resources/V4/PersonVoicesCollection.php @@ -0,0 +1,60 @@ + $this->collection + ]; + } +} \ No newline at end of file diff --git a/app/Http/Resources/V4/PicturesResource.php b/app/Http/Resources/V4/PicturesResource.php new file mode 100644 index 0000000..5cf1841 --- /dev/null +++ b/app/Http/Resources/V4/PicturesResource.php @@ -0,0 +1,55 @@ +pagination = [ + 'last_visible_page' => $resource->lastPage(), + 'has_next_page' => $resource->hasMorePages() + ]; + + $this->collection = $resource->getCollection(); + + parent::__construct($resource); + } + + /** + * Transform the resource collection into an array. + * + * @param \Illuminate\Http\Request $request + * @return array + */ + public function toArray($request) + { + return [ + 'pagination' => $this->pagination, + 'data' => $this->collection + ]; + } + + public function withResponse($request, $response) + { + $jsonResponse = json_decode($response->getContent(), true); + unset($jsonResponse['links'],$jsonResponse['meta']); + $response->setContent(json_encode($jsonResponse)); + } +} \ No newline at end of file diff --git a/app/Http/Resources/V4/ProducerResource.php b/app/Http/Resources/V4/ProducerResource.php new file mode 100644 index 0000000..c6cf2ac --- /dev/null +++ b/app/Http/Resources/V4/ProducerResource.php @@ -0,0 +1,49 @@ + $this->mal_id, + 'name' => $this->name, + 'url' => $this->url, + 'count' => $this->count + ]; + } +} \ No newline at end of file diff --git a/app/Http/Resources/V4/ProfileAboutResource.php b/app/Http/Resources/V4/ProfileAboutResource.php new file mode 100644 index 0000000..3408db4 --- /dev/null +++ b/app/Http/Resources/V4/ProfileAboutResource.php @@ -0,0 +1,38 @@ + $this->about + ]; + } +} \ No newline at end of file diff --git a/app/Http/Resources/V4/ProfileFavoritesResource.php b/app/Http/Resources/V4/ProfileFavoritesResource.php new file mode 100644 index 0000000..3830f02 --- /dev/null +++ b/app/Http/Resources/V4/ProfileFavoritesResource.php @@ -0,0 +1,108 @@ +favorites; + } +} \ No newline at end of file diff --git a/app/Http/Resources/V4/ProfileHistoryResource.php b/app/Http/Resources/V4/ProfileHistoryResource.php new file mode 100644 index 0000000..4d5b31b --- /dev/null +++ b/app/Http/Resources/V4/ProfileHistoryResource.php @@ -0,0 +1,53 @@ +last_updates; + } +} \ No newline at end of file diff --git a/app/Http/Resources/V4/ProfileResource.php b/app/Http/Resources/V4/ProfileResource.php new file mode 100644 index 0000000..699b155 --- /dev/null +++ b/app/Http/Resources/V4/ProfileResource.php @@ -0,0 +1,324 @@ + $this->mal_id, + 'username' => $this->username, + 'url' => $this->url, + 'images' => $this->images, + 'last_online' => $this->last_online, + 'gender' => $this->gender, + 'birthday' => $this->birthday, + 'location' => $this->location, + 'joined' => $this->joined, + ]; + } +} \ No newline at end of file diff --git a/app/Http/Resources/V4/ProfileStatisticsResource.php b/app/Http/Resources/V4/ProfileStatisticsResource.php new file mode 100644 index 0000000..87d9750 --- /dev/null +++ b/app/Http/Resources/V4/ProfileStatisticsResource.php @@ -0,0 +1,151 @@ + $this->anime_stats, + 'manga' => $this->manga_stats, + ]; + } +} \ No newline at end of file diff --git a/app/Http/Resources/V4/RecommendationsResource.php b/app/Http/Resources/V4/RecommendationsResource.php new file mode 100644 index 0000000..c09b1aa --- /dev/null +++ b/app/Http/Resources/V4/RecommendationsResource.php @@ -0,0 +1,109 @@ + [ + 'last_visible_page' => $this['last_visible_page'] ?? 1, + 'has_next_page' => $this['has_next_page'] ?? false, + ], + 'data' => $this['results'], + ]; + } +} \ No newline at end of file diff --git a/app/Http/Resources/V4/ReviewsResource.php b/app/Http/Resources/V4/ReviewsResource.php new file mode 100644 index 0000000..35892cf --- /dev/null +++ b/app/Http/Resources/V4/ReviewsResource.php @@ -0,0 +1,236 @@ + [ + 'last_visible_page' => $this['last_visible_page'], + 'has_next_page' => $this['has_next_page'], + ], + 'data' => $this['results'], + ]; + } +} \ No newline at end of file diff --git a/app/Http/Resources/V4/TrendsCollection.php b/app/Http/Resources/V4/TrendsCollection.php new file mode 100644 index 0000000..88890f3 --- /dev/null +++ b/app/Http/Resources/V4/TrendsCollection.php @@ -0,0 +1,55 @@ +pagination = [ + 'last_visible_page' => $resource->lastPage(), + 'has_next_page' => $resource->hasMorePages() + ]; + + $this->collection = $resource->getCollection(); + + parent::__construct($resource); + } + + /** + * Transform the resource collection into an array. + * + * @param Request $request + * @return array + */ + public function toArray($request) + { + return [ + 'pagination' => $this->pagination, + 'data' => $this->collection + ]; + } + + public function withResponse($request, $response) + { + $jsonResponse = json_decode($response->getContent(), true); + unset($jsonResponse['links'],$jsonResponse['meta']); + $response->setContent(json_encode($jsonResponse)); + } +} \ No newline at end of file diff --git a/app/Http/Resources/V4/TrendsResource.php b/app/Http/Resources/V4/TrendsResource.php new file mode 100644 index 0000000..55b74e9 --- /dev/null +++ b/app/Http/Resources/V4/TrendsResource.php @@ -0,0 +1,17 @@ + $this['_id'], + 'count' => $this['count'], + ]; + } +} \ No newline at end of file diff --git a/app/Http/Resources/V4/UserCollection.php b/app/Http/Resources/V4/UserCollection.php new file mode 100644 index 0000000..b7f90a8 --- /dev/null +++ b/app/Http/Resources/V4/UserCollection.php @@ -0,0 +1,25 @@ + $this->collection + ]; + } +} \ No newline at end of file diff --git a/app/Http/Resources/V4/UserProfileAnimeListCollection.php b/app/Http/Resources/V4/UserProfileAnimeListCollection.php new file mode 100644 index 0000000..471a55e --- /dev/null +++ b/app/Http/Resources/V4/UserProfileAnimeListCollection.php @@ -0,0 +1,29 @@ + $this->collection + ]; + } +} \ No newline at end of file diff --git a/app/Http/Resources/V4/UserProfileAnimeListResource.php b/app/Http/Resources/V4/UserProfileAnimeListResource.php new file mode 100644 index 0000000..3c72626 --- /dev/null +++ b/app/Http/Resources/V4/UserProfileAnimeListResource.php @@ -0,0 +1,78 @@ + 'airing', + JikanConstants::STATUS_ANIME_FINISHED => 'complete', + JikanConstants::STATUS_ANIME_NOT_YET_AIRED => 'not_yet_aired' + ]; + /** + * Transform the resource into an array. + * + * @param \Illuminate\Http\Request $request + * @return array + */ + public function toArray($request) + { + $startDate = $this['start_date'] ?? 'Not Available'; + $endDate = $this['end_date'] ?? '?'; + $startDate = strtotime($startDate); + $endDate = strtotime($endDate); + $dateRange = new DateRange( + date('M j, Y', $startDate) . ' to ' . date('M j, Y', $endDate) + ); + + return [ + 'watching_status' => $this['watching_status'], + 'score' => $this['score'], + 'episodes_watched' => $this['watched_episodes'], + 'tags' => $this['tags'], + 'is_rewatching' => $this['is_rewatching'], + 'watch_start_date' => $this['watch_start_date'], + 'watch_end_date' => $this['watch_end_date'], + 'days' => $this['days'], + 'storage' => $this['storage'], + 'priority' => $this['priority'], + 'anime' => [ + 'mal_id' => $this['mal_id'], + 'title' => $this['title'], + 'url' => $this['url'], + 'images' => $this['images'], + 'type' => $this['type'], + 'season' => strtolower($this['season_name']), + 'year' => $this['season_year'], + 'episodes' => $this['total_episodes'], + 'rating' => $this['rating'], // @todo make same as GET /anime + 'airing' => self::VALID_AIRING_STATUS[$this['airing_status']] == JikanConstants::USER_ANIME_LIST_CURRENTLY_AIRING, + 'aired' => [ + 'from' => $dateRange->getFrom()->format('c'), + 'to' => $dateRange->getUntil()->format('c'), + 'prop' => [ + 'from' => [ + 'day' => $dateRange->getFromProp()->getDay(), + 'month' => $dateRange->getFromProp()->getMonth(), + 'year' => $dateRange->getFromProp()->getYear(), + ], + 'to' => [ + 'day' => $dateRange->getUntilProp()->getDay(), + 'month' => $dateRange->getUntilProp()->getMonth(), + 'year' => $dateRange->getUntilProp()->getYear(), + ] + ], + 'string' => (string) $dateRange + ], + 'studios' => $this['studios'], + 'licensors' => $this['licensors'], + ] + ]; + } +} \ No newline at end of file diff --git a/app/Http/Resources/V4/UserProfileMangaListCollection.php b/app/Http/Resources/V4/UserProfileMangaListCollection.php new file mode 100644 index 0000000..fd12219 --- /dev/null +++ b/app/Http/Resources/V4/UserProfileMangaListCollection.php @@ -0,0 +1,28 @@ + $this->collection + ]; + } +} \ No newline at end of file diff --git a/app/Http/Resources/V4/UserProfileMangaListResource.php b/app/Http/Resources/V4/UserProfileMangaListResource.php new file mode 100644 index 0000000..b4b4e86 --- /dev/null +++ b/app/Http/Resources/V4/UserProfileMangaListResource.php @@ -0,0 +1,79 @@ + 'publishing', + JikanConstants::STATUS_MANGA_FINISHED => 'complete', + JikanConstants::STATUS_MANGA_NOT_YET_PUBLISHED => 'not_yet_published', + JikanConstants::STATUS_MANGA_ON_HIATUS => 'on_hiatus', + JikanConstants::STATUS_MANGA_DISCONTINUED => 'discontinued' + ]; + /** + * Transform the resource into an array. + * + * @param \Illuminate\Http\Request $request + * @return array + */ + public function toArray($request) + { + $startDate = $this['start_date'] ?? 'Not Available'; + $endDate = $this['end_date'] ?? '?'; + $startDate = strtotime($startDate); + $endDate = strtotime($endDate); + $dateRange = new DateRange( + date('M j, Y', $startDate) . ' to ' . date('M j, Y', $endDate) + ); + + return [ + 'reading_status' => $this['reading_status'], + 'score' => $this['score'], + 'chapters_read' => $this['read_chapters'], + 'volumes_read' => $this['read_volumes'], + 'tags' => $this['tags'], + 'is_rereading' => $this['is_rereading'], + 'read_start_date' => $this['read_start_date'], + 'read_end_date' => $this['read_end_date'], + 'days' => $this['days'], + 'retail' => $this['retail'], + 'priority' => $this['priority'], + 'manga' => [ + 'mal_id' => $this['mal_id'], + 'title' => $this['title'], + 'url' => $this['url'], + 'images' => $this['images'], + 'type' => $this['type'], + 'chapters' => $this['total_chapters'], + 'volumes' => $this['total_volumes'], + 'publishing' => self::VALID_PUBLISHING_STATUS[$this['publishing_status']] == JikanConstants::USER_MANGA_LIST_CURRENTLY_PUBLISHING, + 'published' => [ + 'from' => $dateRange->getFrom()->format('c'), + 'to' => $dateRange->getUntil()->format('c'), + 'prop' => [ + 'from' => [ + 'day' => $dateRange->getFromProp()->getDay(), + 'month' => $dateRange->getFromProp()->getMonth(), + 'year' => $dateRange->getFromProp()->getYear(), + ], + 'to' => [ + 'day' => $dateRange->getUntilProp()->getDay(), + 'month' => $dateRange->getUntilProp()->getMonth(), + 'year' => $dateRange->getUntilProp()->getYear(), + ] + ], + 'string' => (string) $dateRange + ], + 'magazines' => $this['magazines'], + ] + ]; + } +} \ No newline at end of file diff --git a/app/Http/Resources/V4/UserUpdatesResource.php b/app/Http/Resources/V4/UserUpdatesResource.php new file mode 100644 index 0000000..01c4d31 --- /dev/null +++ b/app/Http/Resources/V4/UserUpdatesResource.php @@ -0,0 +1,125 @@ +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, @@ -71,10 +68,9 @@ class UpdateCacheJob extends Job ); $cache = json_decode($response->getBody()->getContents(), true); - unset($cache['request_hash'], $cache['request_cached'], $cache['request_cache_expiry']); + unset($cache['fingerprint'], $cache['request_cached'], $cache['request_cache_expiry']); $cache = json_encode($cache); - Cache::forever($this->fingerprint, $cache); Cache::forever($this->cacheExpiryFingerprint, time() + $this->requestCacheTtl); app('redis')->del($queueFingerprint); diff --git a/app/Jobs/UpdateDatabaseJob.php b/app/Jobs/UpdateDatabaseJob.php new file mode 100644 index 0000000..0a3c808 --- /dev/null +++ b/app/Jobs/UpdateDatabaseJob.php @@ -0,0 +1,83 @@ +table = $table; + $this->fingerprint = HttpHelper::resolveRequestFingerprint($request); + + $this->requestType = HttpHelper::requestType($request); + $this->requestCacheTtl = HttpHelper::requestCacheExpiry($this->requestType); + } + + public function handle() : void + { + + $response = app('GuzzleClient') + ->request( + 'GET', + env('APP_URL') . $this->requestUri, + [ + 'headers' => [ + 'auth' => env('APP_KEY') // skips middleware + ] + ] + ); + + $cache = json_decode($response->getBody()->getContents(), true); + unset($cache['request_hash'], $cache['request_cached'], $cache['request_cache_expiry'], $cache['DEVELOPMENT_NOTICE'], $cache['MIGRATION']); + $cache = json_encode($cache); + + DB::table($this->table) + ->where('request_hash', $this->fingerprint) + ->update(array_merge( + [ + 'expiresAt' => new UTCDateTime((time()+$this->requestCacheTtl)*1000), + 'request_hash' => $this->fingerprint + ], + json_decode($cache, true) + )); + + sleep((int) env('QUEUE_DELAY_PER_JOB', 5)); + } + + public function failed(\Exception $e) + { + Log::error($e->getMessage()); + } +} diff --git a/app/Listeners/SourceHeartbeatListener.php b/app/Listeners/SourceHeartbeatListener.php new file mode 100644 index 0000000..dfa5c8a --- /dev/null +++ b/app/Listeners/SourceHeartbeatListener.php @@ -0,0 +1,156 @@ +logger = new Logger('source-health-monitor'); + $this->logger->pushHandler(new StreamHandler(storage_path().'/logs/source-health-monitor.log'), env('APP_DEBUG') ? Logger::DEBUG : Logger::WARNING); + + if (SourceHeartbeatProvider::isFailoverEnabled()) { + $lastFailoverLockTimestamp = $this->getLastFailoverLockTimestamp(); + $this->logger->debug('Failover is RUNNING'); + + // Disable failover if it has expired + if (time() > ($lastFailoverLockTimestamp + env('SOURCE_BAD_HEALTH_RECHECK'))) { + // Disable failover if successful requests score + $this->attemptDisableFailover(); + } + } + } + + /** + * Handle the event. + * + * @param ExampleEvent $event + * @return void + */ + public function handle(SourceHeartbeatEvent $event) + { + $eventCount = $this->insertFail($event); + $this->logger->debug('Event count: '.$eventCount); + + if ($this->getSuccessfulRequestsScore() <= 0.25) { + $this->enableFailover(); + } + } + + private function insertFail(SourceHeartbeatEvent $event) : int + { + $fails = $this->getRecentFails(); + $fails[] = [time(), $event->status, $event->health]; + + $failsJson = json_encode($fails); + Storage::put('failovers.json', $failsJson); + + return count($fails); + } + + private function enableFailover() + { + // create lock file + Storage::put('source_failover.lock', ''); + $this->logger->debug('Failover ENABLED'); + } + + private function disableFailover() + { + // delete lock file + Storage::delete('source_failover.lock'); + + // Delete meta + Storage::delete('failovers.json'); + } + + private function attemptDisableFailover() + { + $score = $this->getSuccessfulRequestsScore(); + + if ($score >= env('SOURCE_GOOD_HEALTH_SCORE', 0.9)) { + $this->disableFailover(); + $this->logger->debug('Failover disabled; Score: '.$score); + $this->logger->debug('Failover DISABLED'); + return true; + } + + return false; + } + + private function getLastFailoverLockTimestamp() + { + try { + return Storage::lastModified('source_failover.lock'); + } catch (\Exception $e) { + return 0; + } + } + + private function getRecentFails() + { + $fails = []; + + try { + $failsJson = Storage::get('failovers.json'); + $fails = json_decode($failsJson, true); + } catch (\Exception $e) { + } + + // remove any fails greater than SOURCE_BAD_HEALTH_RANGE + foreach ($fails as $fail) { + + if (!isset($fail[0])) { + unset($fail); + continue; + } + + if ($fail[0] >= (time()-env('SOURCE_BAD_HEALTH_RANGE'))) { + unset($fail); + } + } + + // slice + if (count($fails) > env('SOURCE_BAD_HEALTH_MAX_STORE')) { + $fails = array_slice($fails, 0 - env('SOURCE_BAD_HEALTH_MAX_STORE')); + } + + return $fails; + } + + private function getSuccessfulRequestsScore() : float + { + $fails = $this->getRecentFails(); + $score = 0; + $totalFails = count($fails) - 1; + + foreach ($fails as $fail) { + if ((int) $fail[2] === SourceHeartbeatEvent::GOOD_HEALTH) { + $score++; + } + } + + $scored = $score / max($totalFails, 1); + $this->logger->debug('Failover successful requests score: '.$scored); + + return $scored; + } +} diff --git a/app/Magazine.php b/app/Magazine.php new file mode 100644 index 0000000..016fb3b --- /dev/null +++ b/app/Magazine.php @@ -0,0 +1,59 @@ +getMagazines(new MagazinesRequest()); + + return json_decode( + app('SerializerV4') + ->serialize($data, 'json'), + true + ); + } +} \ No newline at end of file diff --git a/app/Manga.php b/app/Manga.php new file mode 100644 index 0000000..20b178a --- /dev/null +++ b/app/Manga.php @@ -0,0 +1,63 @@ +getManga(new MangaRequest($id)); + + return HttpHelper::serializeEmptyObjectsControllerLevel( + json_decode( + app('SerializerV4') + ->serialize($data, 'json'), + true + ) + ); + } +} \ No newline at end of file diff --git a/app/Person.php b/app/Person.php new file mode 100644 index 0000000..fefc223 --- /dev/null +++ b/app/Person.php @@ -0,0 +1,65 @@ +attributes['member_favorites']; + } + + + public static function scrape(int $id) + { + $data = app('JikanParser')->getPerson(new PersonRequest($id)); + return json_decode( + app('SerializerV4') + ->serialize($data, 'json'), + true + ); + } +} \ No newline at end of file diff --git a/app/Producer.php b/app/Producer.php new file mode 100644 index 0000000..6d05129 --- /dev/null +++ b/app/Producer.php @@ -0,0 +1,54 @@ +getProducers(new ProducersRequest()); + + return json_decode( + app('SerializerV4') + ->serialize($data, 'json'), + true + ); + } +} \ No newline at end of file diff --git a/app/Profile.php b/app/Profile.php new file mode 100644 index 0000000..bcfa348 --- /dev/null +++ b/app/Profile.php @@ -0,0 +1,46 @@ +getUserProfile(new UserProfileRequest($username)); + + return json_decode( + app('SerializerV4') + ->serialize($data, 'json'), + true + ); + } +} \ No newline at end of file diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 2b48aa9..3993af2 100755 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -3,6 +3,7 @@ namespace App\Providers; use Illuminate\Support\ServiceProvider; +use Jenssegers\Mongodb\Eloquent\Builder; class AppServiceProvider extends ServiceProvider { @@ -16,5 +17,8 @@ class AppServiceProvider extends ServiceProvider // // // $this->app->alias('bugsnag.logger', \Illuminate\Contracts\Logging\Log::class); // $this->app->alias('bugsnag.logger', \Psr\Log\LoggerInterface::class); + Builder::macro('getName', function() { + return 'mongodb'; + }); } } diff --git a/app/Providers/SearchQueryBuilder.php b/app/Providers/SearchQueryBuilder.php index c453552..622486d 100755 --- a/app/Providers/SearchQueryBuilder.php +++ b/app/Providers/SearchQueryBuilder.php @@ -2,11 +2,14 @@ namespace App\Providers; -use Illuminate\Http\Request; +use Jikan\Helper\Constants; use Jikan\Model\Anime\Anime; use Jikan\Request\Search\AnimeSearchRequest; +use Jikan\Request\Search\CharacterSearchRequest; use Jikan\Request\Search\MangaSearchRequest; use Jikan\Request\Search\PersonSearchRequest; +use Jikan\Request\Search\UserSearchRequest; +use \voku\helper\AntiXSS; use Jikan\Helper\Constants as JikanConstants; class SearchQueryBuilder @@ -89,79 +92,87 @@ class SearchQueryBuilder 'desc' => JikanConstants::SEARCH_SORT_DESCENDING, ]; - public static function create(Request $request, $parserRequest) + private const VALID_GENDER = [ + 'any' => JikanConstants::SEARCH_USER_GENDER_ANY, + 'male' => JikanConstants::SEARCH_USER_GENDER_MALE, + 'female' => JikanConstants::SEARCH_USER_GENDER_FEMALE, + 'nonbinary' => JikanConstants::SEARCH_USER_GENDER_NONBINARY + ]; + + public static function create($request) { - $query = $request->get('q'); - $page = $request->get('page'); - $letter = $request->get('letter'); - $subtype = $request->get('type'); - $score = $request->get('score'); - $status = $request->get('status'); - $startDate = $request->get('start_date'); - $endDate = $request->get('end_date'); - $genres = $request->get('genre'); - $genreExclude = $request->get('genre_exclude'); - $sort = $request->get('sort'); - $orderBy = $request->get('order_by'); - $magazine = $request->get('magazine'); - $producer = $request->get('producer'); - $rated = $request->get('rated'); + $xss = new AntiXSS(); // Query - if ($query !== null) { - $parserRequest->setQuery($query); + if (isset($_GET['q'])) { + $request->setQuery( + $xss->xss_clean($_GET['q']) + ); } // Page - if ($page !== null) { - $parserRequest->setPage((int)$page); + if (isset($_GET['page'])) { + $page = (int) $_GET['page']; + $request->setPage($page); } - // Starts with glyph - if (isset($_GET['letter'])) { - $parserRequest->setStartsWithChar(''); + if ( + $request instanceof AnimeSearchRequest + || $request instanceof MangaSearchRequest + || $request instanceof PersonSearchRequest + || $request instanceof CharacterSearchRequest + ) { - if (!empty($_GET['letter'])) { - $letter = - // https://stackoverflow.com/questions/1972100/getting-the-first-character-of-a-string-with-str0#comment27161857_1972111 - mb_substr($letter, 0, 1, 'utf-8'); + // Starts with glyph + if (isset($_GET['letter'])) { + $letter = $xss->xss_clean($_GET['letter']); - $parserRequest->setStartsWithChar($letter); + $request->setStartsWithChar(''); + + if (!empty($_GET['letter'])) { + $letter = + // https://stackoverflow.com/questions/1972100/getting-the-first-character-of-a-string-with-str0#comment27161857_1972111 + mb_substr($letter, 0, 1, 'utf-8'); + + $request->setStartsWithChar($letter); + } } } + // Anime & Manga - if ($parserRequest instanceof AnimeSearchRequest || $parserRequest instanceof MangaSearchRequest) { + if ($request instanceof AnimeSearchRequest || $request instanceof MangaSearchRequest) { // Type - if ($subtype !== null) { - $subtype = strtolower($subtype); + if (isset($_GET['type'])) { + $subtype = strtolower($xss->xss_clean($_GET['type'])); if (array_key_exists($subtype, self::VALID_SUB_TYPES)) { - $parserRequest->setType(self::VALID_SUB_TYPES[$subtype]); + $request->setType(self::VALID_SUB_TYPES[$subtype]); } } // Score - if ($score !== null) { - $score = (float) $score; + if (isset($_GET['score'])) { + $score = (float) $xss->xss_clean($_GET['score']); if ($score >= 0.0 && $score <= 10.0) { - $parserRequest->setScore($score); + $request->setScore($score); } } // Status - if ($status !== null) { - $status = strtolower($status); + if (isset($_GET['status'])) { + $status = strtolower($xss->xss_clean($_GET['status'])); if (array_key_exists($status, self::VALID_STATUS)) { - $parserRequest->setStatus(self::VALID_STATUS[$status]); + $request->setStatus(self::VALID_STATUS[$status]); } } // Start Date - if ($startDate) { + if (isset($_GET['start_date'])) { + $startDate = $xss->xss_clean($_GET['start_date']); if (preg_match("~[0-9]{4}-[0-9]{2}-[0-9]{2}~", $startDate)) { $startDate = explode("-", $startDate); - $parserRequest->setStartDate( + $request->setStartDate( (int) $startDate[2], (int) $startDate[1], (int) $startDate[0] @@ -170,10 +181,11 @@ class SearchQueryBuilder } // End Date - if ($endDate) { + if (isset($_GET['end_date'])) { + $endDate = $xss->xss_clean($_GET['end_date']); if (preg_match("~[0-9]{4}-[0-9]{2}-[0-9]{2}~", $endDate)) { $endDate = explode("-", $endDate); - $parserRequest->setEndDate( + $request->setEndDate( (int) $endDate[2], (int) $endDate[1], (int) $endDate[0] @@ -181,91 +193,125 @@ class SearchQueryBuilder } } - // Genre - if ($genres !== null && \is_string($genres) && strpos($genres, ',')) { - $genres = explode(',', $genres); + // GenreAnime + if (isset($_GET['genre']) && \is_string($_GET['genre']) && strpos($_GET['genre'], ',')) { + $_GET['genre'] = explode(',', $_GET['genre']); } - if ($genres !== null) { - if (\is_array($genres)) { - foreach ($genres as $genre) { + if (isset($_GET['genre'])) { + if (\is_array($_GET['genre'])) { + foreach ($_GET['genre'] as $genre) { $genre = (int) $genre; if ($genre >= self::VALID_MIN_GENRE && $genre <= self::VALID_MAX_GENRE) { - $parserRequest->setGenre($genre); + $request->setGenre($genre); } } } - if (!\is_array($genres)) { - $genres = (int) $genres; + if (!\is_array($_GET['genre'])) { + $genre = (int) $_GET['genre']; - if ($genres >= self::VALID_MIN_GENRE && $genres <= self::VALID_MAX_GENRE) { - $parserRequest->setGenre($genres); + if ($genre >= self::VALID_MIN_GENRE && $genre <= self::VALID_MAX_GENRE) { + $request->setGenre($genre); } } } // Exclude genre passed for $_GET['genre']. Defaulted to false - if ($genreExclude) { - $parserRequest->setGenreExclude( - ((int) $genreExclude == 1) ? true : false + if (isset($_GET['genre_exclude'])) { + $request->setGenreExclude( + (int) $_GET['genre_exclude'] === 1 ); } // Sort - if ($sort !== null) { + if (isset($_GET['sort'])) { + $order = $xss->xss_clean($_GET['sort']); - if (array_key_exists($sort, self::VALID_SORT)) { - $parserRequest->setSort(self::VALID_SORT[$sort]); + if (array_key_exists($order, self::VALID_SORT)) { + $request->setSort(self::VALID_SORT[$order]); } } } // Anime - if ($parserRequest instanceof AnimeSearchRequest) { + if ($request instanceof AnimeSearchRequest) { // Rating/Rated - if ($rated !== null) { - $rated = strtolower($rated); + if (isset($_GET['rated'])) { + $rated = strtolower($xss->xss_clean($_GET['rated'])); if (array_key_exists($rated, self::VALID_RATING)) { - $parserRequest->setRated(self::VALID_RATING[$rated]); + $request->setRated(self::VALID_RATING[$rated]); } } // Producer - if ($producer !== null) { - $producer = (int) $producer; + if (isset($_GET['producer'])) { + $producer = (int) $_GET['producer']; - $parserRequest->setProducer($producer); + $request->setProducer($producer); } // Order By - if ($orderBy) { + if (isset($_GET['order_by'])) { + $order = $xss->xss_clean($_GET['order_by']); - if (array_key_exists($orderBy, self::VALID_ANIME_ORDER_BY)) { - $parserRequest->setOrderBy(self::VALID_ANIME_ORDER_BY[$orderBy]); + if (array_key_exists($order, self::VALID_ANIME_ORDER_BY)) { + $request->setOrderBy(self::VALID_ANIME_ORDER_BY[$order]); } } } // Manga - if ($parserRequest instanceof MangaSearchRequest) { + if ($request instanceof MangaSearchRequest) { // Magazine - if ($magazine !== null) { - $producer = (int) $magazine; + if (isset($_GET['magazine'])) { + $producer = (int) $_GET['magazine']; - $parserRequest->setMagazine($magazine); + $request->setMagazine($producer); } // Order By - if ($orderBy !== null) { + if (isset($_GET['order_by'])) { + $order = $xss->xss_clean($_GET['order_by']); - if (array_key_exists($orderBy, self::VALID_MANGA_ORDER_BY)) { - $parserRequest->setOrderBy(self::VALID_MANGA_ORDER_BY[$orderBy]); + if (array_key_exists($order, self::VALID_MANGA_ORDER_BY)) { + $request->setOrderBy(self::VALID_MANGA_ORDER_BY[$order]); } } } - return $parserRequest; + // Users + if ($request instanceof UserSearchRequest) { + // Gender + if (isset($_GET['gender'])) { + $gender = $xss->xss_clean($_GET['gender']); + + if (array_key_exists($gender, self::VALID_GENDER)) { + $request->setGender(self::VALID_GENDER[$gender]); + } + } + + // Location + if (isset($_GET['location'])) { + $location = $xss->xss_clean($_GET['location']); + + $request->setLocation($location); + } + + // Max Age + if (isset($_GET['max-age'])) { + $maxAge = (int) $_GET['max-age']; + $request->setMaxAge($maxAge); + } + + // Min Age + if (isset($_GET['min-age'])) { + $maxAge = (int) $_GET['min-age']; + $request->setMaxAge($maxAge); + } + } + + return $request; } } diff --git a/app/Providers/SerializationContextFactory.php b/app/Providers/SerializationContextFactory.php index 1fa9baf..df9ebea 100755 --- a/app/Providers/SerializationContextFactory.php +++ b/app/Providers/SerializationContextFactory.php @@ -13,7 +13,7 @@ class SerializationContextFactory implements SerializationContextFactoryInterfac /** * {@InheritDoc} */ - public function createSerializationContext() + public function createSerializationContext(): SerializationContext { return (new SerializationContext()) ->setSerializeNull(true); diff --git a/app/Providers/SerializerFactory.php b/app/Providers/SerializerFactory.php index fcf54a9..a7842f0 100755 --- a/app/Providers/SerializerFactory.php +++ b/app/Providers/SerializerFactory.php @@ -4,75 +4,43 @@ namespace App\Providers; use Jikan\Model\Common\DateRange; use Jikan\Model\Common\MalUrl; +use JMS\Serializer\GraphNavigatorInterface; use JMS\Serializer\Handler\HandlerRegistry; use JMS\Serializer\Serializer; use JMS\Serializer\SerializerBuilder; class SerializerFactory { - public static function createV2(): Serializer + public static function createV4(): Serializer { $serializer = (new SerializerBuilder()) - ->addMetadataDir(__DIR__.'/../../storage/app/metadata.v2') + ->addMetadataDir(__DIR__.'/../../storage/app/metadata.v4') ->configureHandlers( function (HandlerRegistry $registry) { $registry->registerHandler( - 'serialization', - MalUrl::class, - 'json', - \Closure::fromCallable('self::convertMalUrlv2') - ); - - $registry->registerHandler( - 'serialization', - DateRange::class, - 'json', - \Closure::fromCallable('self::convertDateRange') - ); - - $registry->registerHandler( - 'serialization', - \DateTimeImmutable::class, - 'json', - \Closure::fromCallable('self::convertDateTimeImmutable') - ); - } - ) - ->build(); - $serializer->setSerializationContextFactory(new SerializationContextFactory()); - return $serializer; - } - - public static function createV3(): Serializer - { - $serializer = (new SerializerBuilder()) - ->addMetadataDir(__DIR__.'/../../storage/app/metadata.v3') - ->configureHandlers( - function (HandlerRegistry $registry) { - $registry->registerHandler( - 'serialization', + GraphNavigatorInterface::DIRECTION_SERIALIZATION, MalUrl::class, 'json', \Closure::fromCallable('self::convertMalUrl') ); $registry->registerHandler( - 'serialization', + GraphNavigatorInterface::DIRECTION_SERIALIZATION, DateRange::class, 'json', \Closure::fromCallable('self::convertDateRange') ); $registry->registerHandler( - 'serialization', + GraphNavigatorInterface::DIRECTION_SERIALIZATION, \DateTimeImmutable::class, 'json', \Closure::fromCallable('self::convertDateTimeImmutable') ); } ) + ->setSerializationContextFactory(new SerializationContextFactory()) ->build(); - $serializer->setSerializationContextFactory(new SerializationContextFactory()); return $serializer; } diff --git a/app/Providers/SourceHeartbeatProvider.php b/app/Providers/SourceHeartbeatProvider.php new file mode 100644 index 0000000..626acfd --- /dev/null +++ b/app/Providers/SourceHeartbeatProvider.php @@ -0,0 +1,89 @@ + [ + 'App\Listeners\SourceHeartbeatListener', + ], + ]; + + public static function isFailoverEnabled() : bool + { + return Storage::exists('source_failover.lock'); + } + + public static function getLastDowntime() : int + { + try { + return Storage::lastModified('source_failover.lock'); + } catch (\Exception $e) { + return 0; + } + } + + public static function getHeartbeatScore() : float + { + try { + $failsJson = Storage::get('failovers.json'); + $fails = json_decode($failsJson, true); + } catch (\Exception $e) { + $fails = []; + } + + // remove any fails greater than SOURCE_BAD_HEALTH_RANGE + foreach ($fails as $fail) { + + if ($fail[0] >= (time()-env('SOURCE_BAD_HEALTH_RANGE'))) { + unset($fail); + } + } + + // slice + if (count($fails) > env('SOURCE_BAD_HEALTH_MAX_STORE')) { + $fails = array_slice($fails, 0 - env('SOURCE_BAD_HEALTH_MAX_STORE')); + } + + $score = 0; + $totalFails = count($fails) - 1; + + foreach ($fails as $fail) { + if ((int) $fail[2] === SourceHeartbeatEvent::GOOD_HEALTH) { + $score++; + } + } + + $scored = $score / max($totalFails, 1); + + return $scored; + } + + public static function getHeartbeatStatus() : string + { + $score = self::getHeartbeatScore(); + + if ($score > 0.5 && $score < env('SOURCE_GOOD_HEALTH_SCORE')) { + return "LEARNING"; + } + + if ($score <= 0.5) { + return "UNHEALTHY"; + } + + return "HEALTHY"; + } +} diff --git a/bootstrap/app.php b/bootstrap/app.php index 65ba942..aedc3e0 100755 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -1,5 +1,7 @@ register(Jenssegers\Mongodb\MongodbServiceProvider::class); + + $app->withFacades(); $app->withEloquent(); +$app->configure('swagger-lume'); + /* |-------------------------------------------------------------------------- | Register Container Bindings @@ -55,7 +60,6 @@ $app->singleton( App\Console\Kernel::class ); - /* |-------------------------------------------------------------------------- | Register Middleware @@ -67,12 +71,22 @@ $app->singleton( | */ +$globalMiddleware = []; + +if (env('INSIGHTS', false)) { + $globalMiddleware[] = \App\Http\Middleware\Insights::class; +} + +$app->middleware($globalMiddleware); + $app->routeMiddleware([ - 'meta' => App\Http\Middleware\Meta::class, - 'jikan-response' => App\Http\Middleware\JikanResponseHandler::class, - 'throttle' => App\Http\Middleware\Throttle::class, - 'etag' => \App\Http\Middleware\EtagMiddleware::class, - 'microcaching' => \App\Http\Middleware\MicroCaching::class +// 'slave-auth' => App\Http\Middleware\SlaveAuthentication::class, +// 'meta' => App\Http\Middleware\Meta::class, +// 'cache-resolver' => App\Http\Middleware\CacheResolver::class, +// 'throttle' => App\Http\Middleware\Throttle::class, +// 'etag' => \App\Http\Middleware\EtagMiddleware::class, + 'microcaching' => \App\Http\Middleware\MicroCaching::class, + 'source-health-monitor' => SourceHeartbeatMonitor::class, ]); /* @@ -86,19 +100,46 @@ $app->routeMiddleware([ | */ +if (env('CACHING')) { + $app->configure('cache'); + $app->register(Illuminate\Redis\RedisServiceProvider::class); +} + $app->configure('database'); $app->configure('queue'); -$app->configure('cache'); +$app->configure('controller-to-table-mapping'); +$app->configure('controller'); -$app->register(Illuminate\Redis\RedisServiceProvider::class); +$app->register(\SwaggerLume\ServiceProvider::class); $app->register(Flipbox\LumenGenerator\LumenGeneratorServiceProvider::class); +$app->register(\App\Providers\SourceHeartbeatProvider::class); +$app->register(Illuminate\Database\Eloquent\LegacyFactoryServiceProvider::class); -$guzzleClient = new \GuzzleHttp\Client(); -$app->instance('GuzzleClient', $guzzleClient); +if (env('REPORTING') && env('REPORTING_DRIVER') === 'sentry') { + $app->register(\Sentry\Laravel\ServiceProvider::class); + // Sentry Performance Monitoring (optional) + $app->register(\Sentry\Laravel\Tracing\ServiceProvider::class); +} -$jikan = new \Jikan\MyAnimeList\MalClient(app('GuzzleClient')); +// Guzzle removed as of lumen 8.x +//$guzzleClient = new \GuzzleHttp\Client([ +// 'timeout' => env('SOURCE_TIMEOUT', 5), +// 'connect_timeout' => env('SOURCE_CONNECT_TIMEOUT', 5) +//]); +//$app->instance('GuzzleClient', $guzzleClient); + +$httpClient = \Symfony\Component\HttpClient\HttpClient::create( + [ + 'timeout' => env('SOURCE_TIMEOUT', 1) + ] +); +$app->instance('HttpClient', $httpClient); + +$jikan = new \Jikan\MyAnimeList\MalClient(app('HttpClient')); $app->instance('JikanParser', $jikan); +$app->instance('SerializerV4', SerializerFactory::createV4()); + /* |-------------------------------------------------------------------------- @@ -112,54 +153,51 @@ $app->instance('JikanParser', $jikan); */ $commonMiddleware = [ - 'meta', - 'etag', +// 'slave-auth', +// 'meta', +// 'etag', +// 'database-resolver', +// 'cache-resolver', +// 'throttle' + 'source-health-monitor', 'microcaching', - 'jikan-response', - 'throttle' ]; -/*$app->router->group( + +$app->router->group( [ 'prefix' => 'v4', - 'namespace' => 'App\Http\Controllers\V4', + 'namespace' => env('SOURCE') === 'local' ? 'App\Http\Controllers\V4DB' : 'App\Http\Controllers\V4', 'middleware' => $commonMiddleware ], function ($router) { require __DIR__.'/../routes/web.v4.php'; } -);*/ - -$app->router->group( - [ - 'prefix' => 'v3', - 'namespace' => 'App\Http\Controllers\V3', - 'middleware' => $commonMiddleware - ], - function ($router) { - require __DIR__.'/../routes/web.v3.php'; - } ); $app->router->group( [ 'prefix' => '/', - 'namespace' => 'App\Http\Controllers\V3', - 'middleware' => $commonMiddleware ], function ($router) { $router->get('/', function () { return response()->json([ - 'NOTICE' => 'Append an API version for API requests. Please check the documentation for the latest and supported versions.', - 'Author' => '@irfanDahir', - 'Discord' => 'http://discord.jikan.moe', - 'Version' => JIKAN_REST_API_VERSION, - 'JikanPHP' => JIKAN_PARSER_VERSION, - 'Website' => 'https://jikan.moe', - 'Docs' => 'https://jikan.docs.apiary.io', - 'GitHub' => 'https://github.com/jikan-me/jikan', - 'PRODUCTION_API_URL' => 'https://api.jikan.moe/v3/', - 'STATUS_URL' => 'https://status.jikan.moe' + 'author_url' => 'https://github.com/irfan-dahir', + 'discord_url' => 'http://discord.jikan.moe', + 'version' => env('APP_VERSION'), + 'parser_version' => JIKAN_PARSER_VERSION, + 'website_url' => 'https://jikan.moe', + 'documentation_url' => 'https://docs.api.jikan.moe/', + 'github_url' => 'https://github.com/jikan-me/jikan-rest', + 'parser_github_url' => 'https://github.com/jikan-me/jikan', + 'production_api_url' => 'https://api.jikan.moe/v4/', + 'status_url' => 'https://status.jikan.moe', + 'myanimelist_heartbeat' => [ + 'status' => \App\Providers\SourceHeartbeatProvider::getHeartbeatStatus(), + 'score' => \App\Providers\SourceHeartbeatProvider::getHeartbeatScore(), + 'down' => \App\Providers\SourceHeartbeatProvider::isFailoverEnabled(), + 'last_downtime' => \App\Providers\SourceHeartbeatProvider::getLastDowntime() + ] ]); }); } @@ -175,7 +213,7 @@ $app->router->group( ->json([ 'status' => 400, 'type' => 'HttpException', - 'message' => 'This version is depreciated. Please check the documentation for the latest and supported versions.', + 'message' => 'This version is discontinued. Please check the documentation for supported version(s).', 'error' => null ], 400); }); @@ -192,7 +230,24 @@ $app->router->group( ->json([ 'status' => 400, 'type' => 'HttpException', - 'message' => 'This version is depreciated. Please check the documentation for the latest and supported versions.', + 'message' => 'This version is discontinued. Please check the documentation for supported version(s).', + 'error' => null + ], 400); + }); + } +); + +$app->router->group( + [ + 'prefix' => 'v3', + ], + function ($router) { + $router->get('/', function () { + return response() + ->json([ + 'status' => 400, + 'type' => 'HttpException', + 'message' => 'This version is discontinued. Please check the documentation for supported version(s).', 'error' => null ], 400); }); diff --git a/composer.json b/composer.json index 7d67a0c..6545f27 100755 --- a/composer.json +++ b/composer.json @@ -5,27 +5,37 @@ "license": "MIT", "type": "project", "require": { - "php": ">=7.4", - "laravel/lumen-framework": "5.8.*", - "vlucas/phpdotenv": "^3.3", - "danielmewes/php-rql": "dev-master", - "illuminate/redis": "^5.5", - "predis/predis": "^1.1", - "divineomega/cachetphp": "^0.2.0", - "jms/serializer": "^1.13", - "symfony/yaml": "^4.1", - "fabpot/goutte": "3.2.3", - "jikan-me/jikan": "^2.0", + "php": "^7.4|^8.0", "ext-json": "*", - "ocramius/package-versions": "^1.4", - "flipbox/lumen-generator": "^5.6" + "ext-mongodb": "*", + "danielmewes/php-rql": "dev-master", + "darkaonline/swagger-lume": "8.*", + "divineomega/cachetphp": "^0.2.0", + "fabpot/goutte": "^4.0", + "flipbox/lumen-generator": "^8", + "illuminate/redis": "^8", + "jenssegers/mongodb": "^3.8", + "jikan-me/jikan": "3.0.0.x-dev", + "jms/serializer": "^3.0", + "laravel/legacy-factories": "^1.1", + "laravel/lumen-framework": "^8.0", + "league/flysystem": "^1.0", + "ocramius/package-versions": "^2.5", + "predis/predis": "^1.1", + "sentry/sentry-laravel": "^2.8", + "symfony/yaml": "^4.1", + "vlucas/phpdotenv": "^5", + "zircote/swagger-php": "3.*" }, "require-dev": { - "fzaninotto/faker": "~1.4", - "phpunit/phpunit": "~7.0", - "mockery/mockery": "~0.9" + "mockery/mockery": "^1.3.1", + "phpunit/phpunit": "^8.5" }, "autoload": { + "classmap": [ + "database/seeds", + "database/factories" + ], "psr-4": { "App\\": "app/" } @@ -39,14 +49,13 @@ "scripts": { "post-root-package-install": [ "php -r \"copy('.env.dist', '.env');\"" - ], - "server:run": [ - "php -S localhost:8000 -t public" ] }, "minimum-stability": "dev", "prefer-stable": true, "config": { + "preferred-install": "dist", + "sort-packages": true, "optimize-autoloader": true } } diff --git a/composer.lock b/composer.lock index 253b6ad..5fb44a3 100755 --- a/composer.lock +++ b/composer.lock @@ -4,35 +4,93 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "7fde059e93f03a4dc4b493760d161927", + "content-hash": "9d047984b287a81d988936ead7f47eb5", "packages": [ { - "name": "classpreloader/classpreloader", - "version": "3.2.1", + "name": "brick/math", + "version": "0.9.3", "source": { "type": "git", - "url": "https://github.com/ClassPreloader/ClassPreloader.git", - "reference": "297db07cabece3946f4a98d23f11f90aa10e1797" + "url": "https://github.com/brick/math.git", + "reference": "ca57d18f028f84f777b2168cd1911b0dee2343ae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ClassPreloader/ClassPreloader/zipball/297db07cabece3946f4a98d23f11f90aa10e1797", - "reference": "297db07cabece3946f4a98d23f11f90aa10e1797", + "url": "https://api.github.com/repos/brick/math/zipball/ca57d18f028f84f777b2168cd1911b0dee2343ae", + "reference": "ca57d18f028f84f777b2168cd1911b0dee2343ae", "shasum": "" }, "require": { - "nikic/php-parser": "^1.0|^2.0|^3.0", - "php": ">=5.5.9" + "ext-json": "*", + "php": "^7.1 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "^4.8|^5.0" + "php-coveralls/php-coveralls": "^2.2", + "phpunit/phpunit": "^7.5.15 || ^8.5 || ^9.0", + "vimeo/psalm": "4.9.2" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.2-dev" + "autoload": { + "psr-4": { + "Brick\\Math\\": "src/" } }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Arbitrary-precision arithmetic library", + "keywords": [ + "Arbitrary-precision", + "BigInteger", + "BigRational", + "arithmetic", + "bigdecimal", + "bignum", + "brick", + "math" + ], + "support": { + "issues": "https://github.com/brick/math/issues", + "source": "https://github.com/brick/math/tree/0.9.3" + }, + "funding": [ + { + "url": "https://github.com/BenMorel", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/brick/math", + "type": "tidelift" + } + ], + "time": "2021-08-15T20:50:18+00:00" + }, + { + "name": "classpreloader/classpreloader", + "version": "4.2.0", + "source": { + "type": "git", + "url": "https://github.com/ClassPreloader/ClassPreloader.git", + "reference": "af9284543aedb45ed58359374918141c0ac7ae34" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ClassPreloader/ClassPreloader/zipball/af9284543aedb45ed58359374918141c0ac7ae34", + "reference": "af9284543aedb45ed58359374918141c0ac7ae34", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "nikic/php-parser": "^4.10.3", + "php": "^7.0.8 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "graham-campbell/analyzer": "^2.4.3 || ^3.0.4", + "phpunit/phpunit": "^6.5.14 || ^7.5.20 || ^8.5.19" + }, + "type": "library", "autoload": { "psr-4": { "ClassPreloader\\": "src/" @@ -49,26 +107,97 @@ }, { "name": "Graham Campbell", - "email": "graham@alt-three.com" + "email": "hello@gjcampbell.co.uk" } ], "description": "Helps class loading performance by generating a single PHP file containing all of the autoloaded files for a specific use case", "keywords": [ "autoload", "class", - "preload" + "preload", + "preloader" ], "support": { "issues": "https://github.com/ClassPreloader/ClassPreloader/issues", - "source": "https://github.com/ClassPreloader/ClassPreloader/tree/3.2" + "source": "https://github.com/ClassPreloader/ClassPreloader/tree/4.2.0" }, "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/classpreloader/classpreloader", "type": "tidelift" } ], - "time": "2020-04-12T22:01:25+00:00" + "time": "2021-08-28T21:56:17+00:00" + }, + { + "name": "clue/stream-filter", + "version": "v1.5.0", + "source": { + "type": "git", + "url": "https://github.com/clue/stream-filter.git", + "reference": "aeb7d8ea49c7963d3b581378955dbf5bc49aa320" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/clue/stream-filter/zipball/aeb7d8ea49c7963d3b581378955dbf5bc49aa320", + "reference": "aeb7d8ea49c7963d3b581378955dbf5bc49aa320", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.36" + }, + "type": "library", + "autoload": { + "psr-4": { + "Clue\\StreamFilter\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering" + } + ], + "description": "A simple and modern approach to stream filtering in PHP", + "homepage": "https://github.com/clue/php-stream-filter", + "keywords": [ + "bucket brigade", + "callback", + "filter", + "php_user_filter", + "stream", + "stream_filter_append", + "stream_filter_register" + ], + "support": { + "issues": "https://github.com/clue/stream-filter/issues", + "source": "https://github.com/clue/stream-filter/tree/v1.5.0" + }, + "funding": [ + { + "url": "https://clue.engineering/support", + "type": "custom" + }, + { + "url": "https://github.com/clue", + "type": "github" + } + ], + "time": "2020-10-02T12:38:20+00:00" }, { "name": "danielmewes/php-rql", @@ -129,6 +258,70 @@ }, "time": "2016-05-31T01:55:32+00:00" }, + { + "name": "darkaonline/swagger-lume", + "version": "8.0", + "source": { + "type": "git", + "url": "https://github.com/DarkaOnLine/SwaggerLume.git", + "reference": "5ee548ccaf487b4561880eb9741975dad3c3dd1a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/DarkaOnLine/SwaggerLume/zipball/5ee548ccaf487b4561880eb9741975dad3c3dd1a", + "reference": "5ee548ccaf487b4561880eb9741975dad3c3dd1a", + "shasum": "" + }, + "require": { + "laravel/lumen-framework": "~6.0|~7.0|^8.0", + "php": ">=7.2", + "swagger-api/swagger-ui": "^3.0", + "zircote/swagger-php": "~2.0|3.*" + }, + "require-dev": { + "fzaninotto/faker": "~1.8", + "mockery/mockery": "1.*", + "phpunit/phpunit": "8.*", + "satooshi/php-coveralls": "^2.0", + "vlucas/phpdotenv": "~3.3|~4.0" + }, + "type": "library", + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "SwaggerLume\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Darius Matulionis", + "email": "darius@matulionis.lt" + } + ], + "description": "Swagger integration to Lumen 5", + "keywords": [ + "laravel", + "lumen", + "swagger" + ], + "support": { + "issues": "https://github.com/DarkaOnLine/SwaggerLume/issues", + "source": "https://github.com/DarkaOnLine/SwaggerLume/tree/8.0" + }, + "funding": [ + { + "url": "https://github.com/DarkaOnLine", + "type": "github" + } + ], + "time": "2020-09-25T10:41:43+00:00" + }, { "name": "divineomega/cachetphp", "version": "v0.2", @@ -173,43 +366,6 @@ }, "time": "2015-10-14T08:29:26+00:00" }, - { - "name": "dnoegel/php-xdg-base-dir", - "version": "v0.1.1", - "source": { - "type": "git", - "url": "https://github.com/dnoegel/php-xdg-base-dir.git", - "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/dnoegel/php-xdg-base-dir/zipball/8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", - "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", - "shasum": "" - }, - "require": { - "php": ">=5.3.2" - }, - "require-dev": { - "phpunit/phpunit": "~7.0|~6.0|~5.0|~4.8.35" - }, - "type": "library", - "autoload": { - "psr-4": { - "XdgBaseDir\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "implementation of xdg base directory specification for php", - "support": { - "issues": "https://github.com/dnoegel/php-xdg-base-dir/issues", - "source": "https://github.com/dnoegel/php-xdg-base-dir/tree/v0.1.1" - }, - "time": "2019-12-04T15:06:13+00:00" - }, { "name": "doctrine/annotations", "version": "1.13.2", @@ -284,37 +440,32 @@ }, { "name": "doctrine/inflector", - "version": "1.4.4", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/doctrine/inflector.git", - "reference": "4bd5c1cdfcd00e9e2d8c484f79150f67e5d355d9" + "reference": "8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/inflector/zipball/4bd5c1cdfcd00e9e2d8c484f79150f67e5d355d9", - "reference": "4bd5c1cdfcd00e9e2d8c484f79150f67e5d355d9", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89", + "reference": "8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0" + "php": "^7.2 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^8.0", + "doctrine/coding-standard": "^8.2", "phpstan/phpstan": "^0.12", "phpstan/phpstan-phpunit": "^0.12", "phpstan/phpstan-strict-rules": "^0.12", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "vimeo/psalm": "^4.10" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, "autoload": { "psr-4": { - "Doctrine\\Common\\Inflector\\": "lib/Doctrine/Common/Inflector", "Doctrine\\Inflector\\": "lib/Doctrine/Inflector" } }, @@ -360,7 +511,7 @@ ], "support": { "issues": "https://github.com/doctrine/inflector/issues", - "source": "https://github.com/doctrine/inflector/tree/1.4.4" + "source": "https://github.com/doctrine/inflector/tree/2.0.4" }, "funding": [ { @@ -376,7 +527,7 @@ "type": "tidelift" } ], - "time": "2021-04-16T17:34:40+00:00" + "time": "2021-10-22T20:16:43+00:00" }, { "name": "doctrine/instantiator", @@ -529,30 +680,32 @@ }, { "name": "dragonmantank/cron-expression", - "version": "v2.3.1", + "version": "v3.1.0", "source": { "type": "git", "url": "https://github.com/dragonmantank/cron-expression.git", - "reference": "65b2d8ee1f10915efb3b55597da3404f096acba2" + "reference": "7a8c6e56ab3ffcc538d05e8155bb42269abf1a0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/65b2d8ee1f10915efb3b55597da3404f096acba2", - "reference": "65b2d8ee1f10915efb3b55597da3404f096acba2", + "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/7a8c6e56ab3ffcc538d05e8155bb42269abf1a0c", + "reference": "7a8c6e56ab3ffcc538d05e8155bb42269abf1a0c", "shasum": "" }, "require": { - "php": "^7.0|^8.0" + "php": "^7.2|^8.0", + "webmozart/assert": "^1.7.0" + }, + "replace": { + "mtdowling/cron-expression": "^1.0" }, "require-dev": { - "phpunit/phpunit": "^6.4|^7.0|^8.0|^9.0" + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-webmozart-assert": "^0.12.7", + "phpunit/phpunit": "^7.0|^8.0|^9.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.3-dev" - } - }, "autoload": { "psr-4": { "Cron\\": "src/Cron/" @@ -563,11 +716,6 @@ "MIT" ], "authors": [ - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - }, { "name": "Chris Tankersley", "email": "chris@ctankersley.com", @@ -581,7 +729,7 @@ ], "support": { "issues": "https://github.com/dragonmantank/cron-expression/issues", - "source": "https://github.com/dragonmantank/cron-expression/tree/v2.3.1" + "source": "https://github.com/dragonmantank/cron-expression/tree/v3.1.0" }, "funding": [ { @@ -589,7 +737,7 @@ "type": "github" } ], - "time": "2020-10-13T00:52:37+00:00" + "time": "2020-11-24T19:55:57+00:00" }, { "name": "egulias/email-validator", @@ -661,34 +809,30 @@ }, { "name": "fabpot/goutte", - "version": "v3.2.3", + "version": "v4.0.1", "source": { "type": "git", "url": "https://github.com/FriendsOfPHP/Goutte.git", - "reference": "3f0eaf0a40181359470651f1565b3e07e3dd31b8" + "reference": "293e754f0be2f1e85f9b31262cb811de39874e03" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FriendsOfPHP/Goutte/zipball/3f0eaf0a40181359470651f1565b3e07e3dd31b8", - "reference": "3f0eaf0a40181359470651f1565b3e07e3dd31b8", + "url": "https://api.github.com/repos/FriendsOfPHP/Goutte/zipball/293e754f0be2f1e85f9b31262cb811de39874e03", + "reference": "293e754f0be2f1e85f9b31262cb811de39874e03", "shasum": "" }, "require": { - "guzzlehttp/guzzle": "^6.0", - "php": ">=5.5.0", - "symfony/browser-kit": "~2.1|~3.0|~4.0", - "symfony/css-selector": "~2.1|~3.0|~4.0", - "symfony/dom-crawler": "~2.1|~3.0|~4.0" + "php": ">=7.1.3", + "symfony/browser-kit": "^4.4|^5.0", + "symfony/css-selector": "^4.4|^5.0", + "symfony/dom-crawler": "^4.4|^5.0", + "symfony/http-client": "^4.4|^5.0", + "symfony/mime": "^4.4|^5.0" }, "require-dev": { - "symfony/phpunit-bridge": "^3.3 || ^4" + "symfony/phpunit-bridge": "^5.0" }, "type": "application", - "extra": { - "branch-alias": { - "dev-master": "3.2-dev" - } - }, "autoload": { "psr-4": { "Goutte\\": "Goutte" @@ -714,29 +858,34 @@ ], "support": { "issues": "https://github.com/FriendsOfPHP/Goutte/issues", - "source": "https://github.com/FriendsOfPHP/Goutte/tree/master" + "source": "https://github.com/FriendsOfPHP/Goutte/tree/v4.0.1" }, - "time": "2018-06-29T15:13:57+00:00" + "time": "2020-10-14T06:49:09+00:00" }, { "name": "flipbox/lumen-generator", - "version": "5.6.10", + "version": "8.2.2", "source": { "type": "git", "url": "https://github.com/flipboxstudio/lumen-generator.git", - "reference": "16f5c25802bf9b77dfcd6f4c0f415372aca2cc90" + "reference": "d9946a97e1ac5534ff1506184db674c1850c2ece" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/flipboxstudio/lumen-generator/zipball/16f5c25802bf9b77dfcd6f4c0f415372aca2cc90", - "reference": "16f5c25802bf9b77dfcd6f4c0f415372aca2cc90", + "url": "https://api.github.com/repos/flipboxstudio/lumen-generator/zipball/d9946a97e1ac5534ff1506184db674c1850c2ece", + "reference": "d9946a97e1ac5534ff1506184db674c1850c2ece", "shasum": "" }, "require": { - "classpreloader/classpreloader": "^3.0", - "illuminate/support": "^5.4", - "psy/psysh": "0.8.*|0.9.*", - "symfony/var-dumper": "^3.1|^4.1|^4.2|^4.3" + "classpreloader/classpreloader": "^3.0|^4.0", + "illuminate/console": "^5.5|^6.0|^7.0|^8.0|^8.17", + "illuminate/filesystem": "^5.5|^6.0|^7.0|^8.0|^8.17", + "illuminate/support": "^5.5|^6.0|^7.0|^8.0|^8.17", + "psy/psysh": "0.9.*|0.10.*", + "symfony/var-dumper": "^4.2|^4.3|^5.0|^5.1|^5.2" + }, + "suggest": { + "anik/form-request": "Required to use form request in Lumen." }, "type": "library", "autoload": { @@ -757,9 +906,71 @@ "description": "A Lumen Generator You Are Missing", "support": { "issues": "https://github.com/flipboxstudio/lumen-generator/issues", - "source": "https://github.com/flipboxstudio/lumen-generator/tree/5.6.10" + "source": "https://github.com/flipboxstudio/lumen-generator/tree/8.2.2" }, - "time": "2019-08-19T07:05:28+00:00" + "time": "2021-10-18T12:30:09+00:00" + }, + { + "name": "graham-campbell/result-type", + "version": "v1.0.4", + "source": { + "type": "git", + "url": "https://github.com/GrahamCampbell/Result-Type.git", + "reference": "0690bde05318336c7221785f2a932467f98b64ca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/0690bde05318336c7221785f2a932467f98b64ca", + "reference": "0690bde05318336c7221785f2a932467f98b64ca", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "phpoption/phpoption": "^1.8" + }, + "require-dev": { + "phpunit/phpunit": "^6.5.14 || ^7.5.20 || ^8.5.19 || ^9.5.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "GrahamCampbell\\ResultType\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "An Implementation Of The Result Type", + "keywords": [ + "Graham Campbell", + "GrahamCampbell", + "Result Type", + "Result-Type", + "result" + ], + "support": { + "issues": "https://github.com/GrahamCampbell/Result-Type/issues", + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.0.4" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type", + "type": "tidelift" + } + ], + "time": "2021-11-21T21:41:47+00:00" }, { "name": "guzzlehttp/guzzle", @@ -834,16 +1045,16 @@ }, { "name": "guzzlehttp/promises", - "version": "1.4.1", + "version": "1.5.1", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "8e7d04f1f6450fef59366c399cfad4b9383aa30d" + "reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/8e7d04f1f6450fef59366c399cfad4b9383aa30d", - "reference": "8e7d04f1f6450fef59366c399cfad4b9383aa30d", + "url": "https://api.github.com/repos/guzzle/promises/zipball/fe752aedc9fd8fcca3fe7ad05d419d32998a06da", + "reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da", "shasum": "" }, "require": { @@ -855,7 +1066,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4-dev" + "dev-master": "1.5-dev" } }, "autoload": { @@ -871,10 +1082,25 @@ "MIT" ], "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, { "name": "Michael Dowling", "email": "mtdowling@gmail.com", "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" } ], "description": "Guzzle promises library", @@ -883,22 +1109,36 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/1.4.1" + "source": "https://github.com/guzzle/promises/tree/1.5.1" }, - "time": "2021-03-07T09:25:29+00:00" + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "time": "2021-10-22T20:56:57+00:00" }, { "name": "guzzlehttp/psr7", - "version": "1.8.2", + "version": "1.8.3", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "dc960a912984efb74d0a90222870c72c87f10c91" + "reference": "1afdd860a2566ed3c2b0b4a3de6e23434a79ec85" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/dc960a912984efb74d0a90222870c72c87f10c91", - "reference": "dc960a912984efb74d0a90222870c72c87f10c91", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/1afdd860a2566ed3c2b0b4a3de6e23434a79ec85", + "reference": "1afdd860a2566ed3c2b0b4a3de6e23434a79ec85", "shasum": "" }, "require": { @@ -935,13 +1175,34 @@ "MIT" ], "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, { "name": "Michael Dowling", "email": "mtdowling@gmail.com", "homepage": "https://github.com/mtdowling" }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, { "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", "homepage": "https://github.com/Tobion" } ], @@ -958,40 +1219,114 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/1.8.2" + "source": "https://github.com/guzzle/psr7/tree/1.8.3" }, - "time": "2021-04-26T09:17:50+00:00" + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "time": "2021-10-05T13:56:00+00:00" }, { - "name": "illuminate/auth", - "version": "v5.8.36", + "name": "http-interop/http-factory-guzzle", + "version": "1.2.0", "source": { "type": "git", - "url": "https://github.com/illuminate/auth.git", - "reference": "59d63d9dfda2836e8a75b4f1c6df8e2be3fb3909" + "url": "https://github.com/http-interop/http-factory-guzzle.git", + "reference": "8f06e92b95405216b237521cc64c804dd44c4a81" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/auth/zipball/59d63d9dfda2836e8a75b4f1c6df8e2be3fb3909", - "reference": "59d63d9dfda2836e8a75b4f1c6df8e2be3fb3909", + "url": "https://api.github.com/repos/http-interop/http-factory-guzzle/zipball/8f06e92b95405216b237521cc64c804dd44c4a81", + "reference": "8f06e92b95405216b237521cc64c804dd44c4a81", "shasum": "" }, "require": { - "illuminate/contracts": "5.8.*", - "illuminate/http": "5.8.*", - "illuminate/queue": "5.8.*", - "illuminate/support": "5.8.*", - "php": "^7.1.3" + "guzzlehttp/psr7": "^1.7||^2.0", + "php": ">=7.3", + "psr/http-factory": "^1.0" + }, + "provide": { + "psr/http-factory-implementation": "^1.0" + }, + "require-dev": { + "http-interop/http-factory-tests": "^0.9", + "phpunit/phpunit": "^9.5" }, "suggest": { - "illuminate/console": "Required to use the auth:clear-resets command (5.8.*).", - "illuminate/queue": "Required to fire login / logout events (5.8.*).", - "illuminate/session": "Required to use the session based guard (5.8.*)." + "guzzlehttp/psr7": "Includes an HTTP factory starting in version 2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Http\\Factory\\Guzzle\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "An HTTP Factory using Guzzle PSR7", + "keywords": [ + "factory", + "http", + "psr-17", + "psr-7" + ], + "support": { + "issues": "https://github.com/http-interop/http-factory-guzzle/issues", + "source": "https://github.com/http-interop/http-factory-guzzle/tree/1.2.0" + }, + "time": "2021-07-21T13:50:14+00:00" + }, + { + "name": "illuminate/auth", + "version": "v8.77.1", + "source": { + "type": "git", + "url": "https://github.com/illuminate/auth.git", + "reference": "02b166738b6e7449e18fe595822abeac59b7e317" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/auth/zipball/02b166738b6e7449e18fe595822abeac59b7e317", + "reference": "02b166738b6e7449e18fe595822abeac59b7e317", + "shasum": "" + }, + "require": { + "illuminate/collections": "^8.0", + "illuminate/contracts": "^8.0", + "illuminate/http": "^8.0", + "illuminate/macroable": "^8.0", + "illuminate/queue": "^8.0", + "illuminate/support": "^8.0", + "php": "^7.3|^8.0" + }, + "suggest": { + "illuminate/console": "Required to use the auth:clear-resets command (^8.0).", + "illuminate/queue": "Required to fire login / logout events (^8.0).", + "illuminate/session": "Required to use the session based guard (^8.0)." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.8-dev" + "dev-master": "8.x-dev" } }, "autoload": { @@ -1015,38 +1350,39 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2019-08-15T12:01:20+00:00" + "time": "2021-12-02T21:22:29+00:00" }, { "name": "illuminate/broadcasting", - "version": "v5.8.36", + "version": "v8.77.1", "source": { "type": "git", "url": "https://github.com/illuminate/broadcasting.git", - "reference": "b1217ccf631e86ed17d59cdd43562555996e9a48" + "reference": "ee28e10ecde83ff179ea998f24625eacdcdba9ee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/broadcasting/zipball/b1217ccf631e86ed17d59cdd43562555996e9a48", - "reference": "b1217ccf631e86ed17d59cdd43562555996e9a48", + "url": "https://api.github.com/repos/illuminate/broadcasting/zipball/ee28e10ecde83ff179ea998f24625eacdcdba9ee", + "reference": "ee28e10ecde83ff179ea998f24625eacdcdba9ee", "shasum": "" }, "require": { "ext-json": "*", - "illuminate/bus": "5.8.*", - "illuminate/contracts": "5.8.*", - "illuminate/queue": "5.8.*", - "illuminate/support": "5.8.*", - "php": "^7.1.3", - "psr/log": "^1.0" + "illuminate/bus": "^8.0", + "illuminate/collections": "^8.0", + "illuminate/contracts": "^8.0", + "illuminate/queue": "^8.0", + "illuminate/support": "^8.0", + "php": "^7.3|^8.0", + "psr/log": "^1.0|^2.0" }, "suggest": { - "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^3.0)." + "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^4.0|^5.0|^6.0|^7.0)." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.8-dev" + "dev-master": "8.x-dev" } }, "autoload": { @@ -1070,32 +1406,36 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2019-06-04T14:33:55+00:00" + "time": "2021-12-16T20:33:46+00:00" }, { "name": "illuminate/bus", - "version": "v5.8.36", + "version": "v8.77.1", "source": { "type": "git", "url": "https://github.com/illuminate/bus.git", - "reference": "6a15b03cdc6739c3f2898d67dc4fe21357d60e07" + "reference": "82ed7d9d6edc625ffe5d01fe17af3e223aed1cb0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/bus/zipball/6a15b03cdc6739c3f2898d67dc4fe21357d60e07", - "reference": "6a15b03cdc6739c3f2898d67dc4fe21357d60e07", + "url": "https://api.github.com/repos/illuminate/bus/zipball/82ed7d9d6edc625ffe5d01fe17af3e223aed1cb0", + "reference": "82ed7d9d6edc625ffe5d01fe17af3e223aed1cb0", "shasum": "" }, "require": { - "illuminate/contracts": "5.8.*", - "illuminate/pipeline": "5.8.*", - "illuminate/support": "5.8.*", - "php": "^7.1.3" + "illuminate/collections": "^8.0", + "illuminate/contracts": "^8.0", + "illuminate/pipeline": "^8.0", + "illuminate/support": "^8.0", + "php": "^7.3|^8.0" + }, + "suggest": { + "illuminate/queue": "Required to use closures when chaining jobs (^7.0)." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.8-dev" + "dev-master": "8.x-dev" } }, "autoload": { @@ -1119,36 +1459,43 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2019-02-18T18:37:54+00:00" + "time": "2021-11-23T19:43:42+00:00" }, { "name": "illuminate/cache", - "version": "v5.8.36", + "version": "v8.77.1", "source": { "type": "git", "url": "https://github.com/illuminate/cache.git", - "reference": "e6acac59f94c6362809b580918f7f3f6142d5796" + "reference": "a5a2f803990a5876d55acd703ab6174a25c3d40f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/cache/zipball/e6acac59f94c6362809b580918f7f3f6142d5796", - "reference": "e6acac59f94c6362809b580918f7f3f6142d5796", + "url": "https://api.github.com/repos/illuminate/cache/zipball/a5a2f803990a5876d55acd703ab6174a25c3d40f", + "reference": "a5a2f803990a5876d55acd703ab6174a25c3d40f", "shasum": "" }, "require": { - "illuminate/contracts": "5.8.*", - "illuminate/support": "5.8.*", - "php": "^7.1.3" + "illuminate/collections": "^8.0", + "illuminate/contracts": "^8.0", + "illuminate/macroable": "^8.0", + "illuminate/support": "^8.0", + "php": "^7.3|^8.0" + }, + "provide": { + "psr/simple-cache-implementation": "1.0" }, "suggest": { - "illuminate/database": "Required to use the database cache driver (5.8.*).", - "illuminate/filesystem": "Required to use the file cache driver (5.8.*).", - "illuminate/redis": "Required to use the redis cache driver (5.8.*)." + "ext-memcached": "Required to use the memcache cache driver.", + "illuminate/database": "Required to use the database cache driver (^8.0).", + "illuminate/filesystem": "Required to use the file cache driver (^8.0).", + "illuminate/redis": "Required to use the redis cache driver (^8.0).", + "symfony/cache": "Required to PSR-6 cache bridge (^5.4)." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.8-dev" + "dev-master": "8.x-dev" } }, "autoload": { @@ -1172,31 +1519,85 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2019-08-18T13:53:57+00:00" + "time": "2021-12-15T14:13:56+00:00" }, { - "name": "illuminate/config", - "version": "v5.8.36", + "name": "illuminate/collections", + "version": "v8.77.1", "source": { "type": "git", - "url": "https://github.com/illuminate/config.git", - "reference": "6dac1dee3fb51704767c69a07aead1bc75c12368" + "url": "https://github.com/illuminate/collections.git", + "reference": "bafdbd033a717aed94e4d023512f2c9eb3e8cd77" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/config/zipball/6dac1dee3fb51704767c69a07aead1bc75c12368", - "reference": "6dac1dee3fb51704767c69a07aead1bc75c12368", + "url": "https://api.github.com/repos/illuminate/collections/zipball/bafdbd033a717aed94e4d023512f2c9eb3e8cd77", + "reference": "bafdbd033a717aed94e4d023512f2c9eb3e8cd77", "shasum": "" }, "require": { - "illuminate/contracts": "5.8.*", - "illuminate/support": "5.8.*", - "php": "^7.1.3" + "illuminate/contracts": "^8.0", + "illuminate/macroable": "^8.0", + "php": "^7.3|^8.0" + }, + "suggest": { + "symfony/var-dumper": "Required to use the dump method (^5.4)." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.8-dev" + "dev-master": "8.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Support\\": "" + }, + "files": [ + "helpers.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Collections package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2021-12-16T15:00:27+00:00" + }, + { + "name": "illuminate/config", + "version": "v8.77.1", + "source": { + "type": "git", + "url": "https://github.com/illuminate/config.git", + "reference": "70973cbbe0cb524658b6eeaa2386dd5b71de4b02" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/config/zipball/70973cbbe0cb524658b6eeaa2386dd5b71de4b02", + "reference": "70973cbbe0cb524658b6eeaa2386dd5b71de4b02", + "shasum": "" + }, + "require": { + "illuminate/collections": "^8.0", + "illuminate/contracts": "^8.0", + "php": "^7.3|^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "8.x-dev" } }, "autoload": { @@ -1220,38 +1621,43 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2019-02-14T12:51:50+00:00" + "time": "2021-08-03T13:42:24+00:00" }, { "name": "illuminate/console", - "version": "v5.8.36", + "version": "v8.77.1", "source": { "type": "git", "url": "https://github.com/illuminate/console.git", - "reference": "e6e4708e6c6baaf92120848e885855ab3d76f30f" + "reference": "5db4a34711a08347fad5b775c58746a7103a3094" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/console/zipball/e6e4708e6c6baaf92120848e885855ab3d76f30f", - "reference": "e6e4708e6c6baaf92120848e885855ab3d76f30f", + "url": "https://api.github.com/repos/illuminate/console/zipball/5db4a34711a08347fad5b775c58746a7103a3094", + "reference": "5db4a34711a08347fad5b775c58746a7103a3094", "shasum": "" }, "require": { - "illuminate/contracts": "5.8.*", - "illuminate/support": "5.8.*", - "php": "^7.1.3", - "symfony/console": "^4.2", - "symfony/process": "^4.2" + "illuminate/collections": "^8.0", + "illuminate/contracts": "^8.0", + "illuminate/macroable": "^8.0", + "illuminate/support": "^8.0", + "php": "^7.3|^8.0", + "symfony/console": "^5.4", + "symfony/process": "^5.4" }, "suggest": { - "dragonmantank/cron-expression": "Required to use scheduling component (^2.0).", - "guzzlehttp/guzzle": "Required to use the ping methods on schedules (^6.0).", - "illuminate/filesystem": "Required to use the generator command (5.8.*)" + "dragonmantank/cron-expression": "Required to use scheduler (^3.0.2).", + "guzzlehttp/guzzle": "Required to use the ping methods on schedules (^6.5.5|^7.0.1).", + "illuminate/bus": "Required to use the scheduled job dispatcher (^8.0).", + "illuminate/container": "Required to use the scheduler (^8.0).", + "illuminate/filesystem": "Required to use the generator command (^8.0).", + "illuminate/queue": "Required to use closures for scheduled jobs (^8.0)." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.8-dev" + "dev-master": "8.x-dev" } }, "autoload": { @@ -1275,32 +1681,34 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2019-08-12T13:08:28+00:00" + "time": "2021-12-14T14:40:44+00:00" }, { "name": "illuminate/container", - "version": "v5.8.36", + "version": "v8.77.1", "source": { "type": "git", "url": "https://github.com/illuminate/container.git", - "reference": "b42e5ef939144b77f78130918da0ce2d9ee16574" + "reference": "6ac391bb27391706c5f921b85060aa2c4ca03fae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/container/zipball/b42e5ef939144b77f78130918da0ce2d9ee16574", - "reference": "b42e5ef939144b77f78130918da0ce2d9ee16574", + "url": "https://api.github.com/repos/illuminate/container/zipball/6ac391bb27391706c5f921b85060aa2c4ca03fae", + "reference": "6ac391bb27391706c5f921b85060aa2c4ca03fae", "shasum": "" }, "require": { - "illuminate/contracts": "5.8.*", - "illuminate/support": "5.8.*", - "php": "^7.1.3", + "illuminate/contracts": "^8.0", + "php": "^7.3|^8.0", "psr/container": "^1.0" }, + "provide": { + "psr/container-implementation": "1.0" + }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.8-dev" + "dev-master": "8.x-dev" } }, "autoload": { @@ -1324,31 +1732,31 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2019-08-20T02:00:23+00:00" + "time": "2021-11-17T15:04:30+00:00" }, { "name": "illuminate/contracts", - "version": "v5.8.36", + "version": "v8.77.1", "source": { "type": "git", "url": "https://github.com/illuminate/contracts.git", - "reference": "00fc6afee788fa07c311b0650ad276585f8aef96" + "reference": "9baa9f781071e67d7b171775bd3be7ead13ddd29" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/contracts/zipball/00fc6afee788fa07c311b0650ad276585f8aef96", - "reference": "00fc6afee788fa07c311b0650ad276585f8aef96", + "url": "https://api.github.com/repos/illuminate/contracts/zipball/9baa9f781071e67d7b171775bd3be7ead13ddd29", + "reference": "9baa9f781071e67d7b171775bd3be7ead13ddd29", "shasum": "" }, "require": { - "php": "^7.1.3", + "php": "^7.3|^8.0", "psr/container": "^1.0", "psr/simple-cache": "^1.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.8-dev" + "dev-master": "8.x-dev" } }, "autoload": { @@ -1372,41 +1780,45 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2019-07-30T13:57:21+00:00" + "time": "2021-12-14T14:40:44+00:00" }, { "name": "illuminate/database", - "version": "v5.8.36", + "version": "v8.77.1", "source": { "type": "git", "url": "https://github.com/illuminate/database.git", - "reference": "ac9ae2d82b8a6137400f17b3eea258be3518daa9" + "reference": "93cd22231b39532799d533281cbffe9a39fb6152" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/database/zipball/ac9ae2d82b8a6137400f17b3eea258be3518daa9", - "reference": "ac9ae2d82b8a6137400f17b3eea258be3518daa9", + "url": "https://api.github.com/repos/illuminate/database/zipball/93cd22231b39532799d533281cbffe9a39fb6152", + "reference": "93cd22231b39532799d533281cbffe9a39fb6152", "shasum": "" }, "require": { "ext-json": "*", - "illuminate/container": "5.8.*", - "illuminate/contracts": "5.8.*", - "illuminate/support": "5.8.*", - "php": "^7.1.3" + "illuminate/collections": "^8.0", + "illuminate/container": "^8.0", + "illuminate/contracts": "^8.0", + "illuminate/macroable": "^8.0", + "illuminate/support": "^8.0", + "php": "^7.3|^8.0", + "symfony/console": "^5.4" }, "suggest": { - "doctrine/dbal": "Required to rename columns and drop SQLite columns (^2.6).", - "fzaninotto/faker": "Required to use the eloquent factory builder (^1.4).", - "illuminate/console": "Required to use the database commands (5.8.*).", - "illuminate/events": "Required to use the observers with Eloquent (5.8.*).", - "illuminate/filesystem": "Required to use the migrations (5.8.*).", - "illuminate/pagination": "Required to paginate the result set (5.8.*)." + "doctrine/dbal": "Required to rename columns and drop SQLite columns (^2.13.3|^3.1.4).", + "fakerphp/faker": "Required to use the eloquent factory builder (^1.9.1).", + "illuminate/console": "Required to use the database commands (^8.0).", + "illuminate/events": "Required to use the observers with Eloquent (^8.0).", + "illuminate/filesystem": "Required to use the migrations (^8.0).", + "illuminate/pagination": "Required to paginate the result set (^8.0).", + "symfony/finder": "Required to use Eloquent model factories (^5.4)." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.8-dev" + "dev-master": "8.x-dev" } }, "autoload": { @@ -1436,34 +1848,34 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2019-10-03T16:22:57+00:00" + "time": "2021-12-20T15:27:32+00:00" }, { "name": "illuminate/encryption", - "version": "v5.8.36", + "version": "v8.77.1", "source": { "type": "git", "url": "https://github.com/illuminate/encryption.git", - "reference": "135c631bab0e0a8b9535b5750687e0a867c85193" + "reference": "3ff5c78f402c81da4b2ad4bef8f747a13e6fb0ff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/encryption/zipball/135c631bab0e0a8b9535b5750687e0a867c85193", - "reference": "135c631bab0e0a8b9535b5750687e0a867c85193", + "url": "https://api.github.com/repos/illuminate/encryption/zipball/3ff5c78f402c81da4b2ad4bef8f747a13e6fb0ff", + "reference": "3ff5c78f402c81da4b2ad4bef8f747a13e6fb0ff", "shasum": "" }, "require": { "ext-json": "*", "ext-mbstring": "*", "ext-openssl": "*", - "illuminate/contracts": "5.8.*", - "illuminate/support": "5.8.*", - "php": "^7.1.3" + "illuminate/contracts": "^8.0", + "illuminate/support": "^8.0", + "php": "^7.3|^8.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.8-dev" + "dev-master": "8.x-dev" } }, "autoload": { @@ -1487,38 +1899,44 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2019-06-11T14:00:26+00:00" + "time": "2021-09-15T14:32:50+00:00" }, { "name": "illuminate/events", - "version": "v5.8.36", + "version": "v8.77.1", "source": { "type": "git", "url": "https://github.com/illuminate/events.git", - "reference": "a85d7c273bc4e3357000c5fc4812374598515de3" + "reference": "b7f06cafb6c09581617f2ca05d69e9b159e5a35d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/events/zipball/a85d7c273bc4e3357000c5fc4812374598515de3", - "reference": "a85d7c273bc4e3357000c5fc4812374598515de3", + "url": "https://api.github.com/repos/illuminate/events/zipball/b7f06cafb6c09581617f2ca05d69e9b159e5a35d", + "reference": "b7f06cafb6c09581617f2ca05d69e9b159e5a35d", "shasum": "" }, "require": { - "illuminate/container": "5.8.*", - "illuminate/contracts": "5.8.*", - "illuminate/support": "5.8.*", - "php": "^7.1.3" + "illuminate/bus": "^8.0", + "illuminate/collections": "^8.0", + "illuminate/container": "^8.0", + "illuminate/contracts": "^8.0", + "illuminate/macroable": "^8.0", + "illuminate/support": "^8.0", + "php": "^7.3|^8.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.8-dev" + "dev-master": "8.x-dev" } }, "autoload": { "psr-4": { "Illuminate\\Events\\": "" - } + }, + "files": [ + "functions.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1536,39 +1954,45 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2019-02-18T18:37:54+00:00" + "time": "2021-09-15T14:32:50+00:00" }, { "name": "illuminate/filesystem", - "version": "v5.8.36", + "version": "v8.77.1", "source": { "type": "git", "url": "https://github.com/illuminate/filesystem.git", - "reference": "494ba903402d64ec49c8d869ab61791db34b2288" + "reference": "48f9d61b5da49f0fa8d9f28db8ae8c0d89e367a6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/filesystem/zipball/494ba903402d64ec49c8d869ab61791db34b2288", - "reference": "494ba903402d64ec49c8d869ab61791db34b2288", + "url": "https://api.github.com/repos/illuminate/filesystem/zipball/48f9d61b5da49f0fa8d9f28db8ae8c0d89e367a6", + "reference": "48f9d61b5da49f0fa8d9f28db8ae8c0d89e367a6", "shasum": "" }, "require": { - "illuminate/contracts": "5.8.*", - "illuminate/support": "5.8.*", - "php": "^7.1.3", - "symfony/finder": "^4.2" + "illuminate/collections": "^8.0", + "illuminate/contracts": "^8.0", + "illuminate/macroable": "^8.0", + "illuminate/support": "^8.0", + "php": "^7.3|^8.0", + "symfony/finder": "^5.4" }, "suggest": { - "league/flysystem": "Required to use the Flysystem local and FTP drivers (^1.0).", + "ext-ftp": "Required to use the Flysystem FTP driver.", + "illuminate/http": "Required for handling uploaded files (^7.0).", + "league/flysystem": "Required to use the Flysystem local and FTP drivers (^1.1).", "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^1.0).", "league/flysystem-cached-adapter": "Required to use the Flysystem cache (^1.0).", - "league/flysystem-rackspace": "Required to use the Flysystem Rackspace driver (^1.0).", - "league/flysystem-sftp": "Required to use the Flysystem SFTP driver (^1.0)." + "league/flysystem-sftp": "Required to use the Flysystem SFTP driver (^1.0).", + "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).", + "symfony/filesystem": "Required to enable support for relative symbolic links (^5.4).", + "symfony/mime": "Required to enable support for guessing extensions (^5.4)." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.8-dev" + "dev-master": "8.x-dev" } }, "autoload": { @@ -1592,31 +2016,31 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2019-08-14T13:38:15+00:00" + "time": "2021-12-20T20:07:00+00:00" }, { "name": "illuminate/hashing", - "version": "v5.8.36", + "version": "v8.77.1", "source": { "type": "git", "url": "https://github.com/illuminate/hashing.git", - "reference": "56a9f294d9615bbbb14e2093fb0537388952cc2c" + "reference": "2617f4de8d0150a3f8641b086fafac8c1e0cdbf2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/hashing/zipball/56a9f294d9615bbbb14e2093fb0537388952cc2c", - "reference": "56a9f294d9615bbbb14e2093fb0537388952cc2c", + "url": "https://api.github.com/repos/illuminate/hashing/zipball/2617f4de8d0150a3f8641b086fafac8c1e0cdbf2", + "reference": "2617f4de8d0150a3f8641b086fafac8c1e0cdbf2", "shasum": "" }, "require": { - "illuminate/contracts": "5.8.*", - "illuminate/support": "5.8.*", - "php": "^7.1.3" + "illuminate/contracts": "^8.0", + "illuminate/support": "^8.0", + "php": "^7.3|^8.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.8-dev" + "dev-master": "8.x-dev" } }, "autoload": { @@ -1640,37 +2064,41 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2019-02-18T18:37:54+00:00" + "time": "2021-10-22T13:20:42+00:00" }, { "name": "illuminate/http", - "version": "v5.8.36", + "version": "v8.77.1", "source": { "type": "git", "url": "https://github.com/illuminate/http.git", - "reference": "cd0f549611de16b323af88478b441e4d52ceef40" + "reference": "42bc285904acdf51e0949f01cb61852d6e20e3e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/http/zipball/cd0f549611de16b323af88478b441e4d52ceef40", - "reference": "cd0f549611de16b323af88478b441e4d52ceef40", + "url": "https://api.github.com/repos/illuminate/http/zipball/42bc285904acdf51e0949f01cb61852d6e20e3e2", + "reference": "42bc285904acdf51e0949f01cb61852d6e20e3e2", "shasum": "" }, "require": { "ext-json": "*", - "illuminate/session": "5.8.*", - "illuminate/support": "5.8.*", - "php": "^7.1.3", - "symfony/http-foundation": "^4.2", - "symfony/http-kernel": "^4.2" + "illuminate/collections": "^8.0", + "illuminate/macroable": "^8.0", + "illuminate/session": "^8.0", + "illuminate/support": "^8.0", + "php": "^7.3|^8.0", + "symfony/http-foundation": "^5.4", + "symfony/http-kernel": "^5.4", + "symfony/mime": "^5.4" }, "suggest": { - "ext-gd": "Required to use Illuminate\\Http\\Testing\\FileFactory::image()." + "ext-gd": "Required to use Illuminate\\Http\\Testing\\FileFactory::image().", + "guzzlehttp/guzzle": "Required to use the HTTP Client (^6.5.5|^7.0.1)." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.8-dev" + "dev-master": "8.x-dev" } }, "autoload": { @@ -1694,32 +2122,32 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2019-09-03T16:36:47+00:00" + "time": "2021-12-17T15:23:31+00:00" }, { "name": "illuminate/log", - "version": "v5.8.36", + "version": "v8.77.1", "source": { "type": "git", "url": "https://github.com/illuminate/log.git", - "reference": "1d23931e0ff74fa461fc44dc1594c66f8f6ad36b" + "reference": "ac693c06fbc60ac344c55925d34aa811b85f1455" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/log/zipball/1d23931e0ff74fa461fc44dc1594c66f8f6ad36b", - "reference": "1d23931e0ff74fa461fc44dc1594c66f8f6ad36b", + "url": "https://api.github.com/repos/illuminate/log/zipball/ac693c06fbc60ac344c55925d34aa811b85f1455", + "reference": "ac693c06fbc60ac344c55925d34aa811b85f1455", "shasum": "" }, "require": { - "illuminate/contracts": "5.8.*", - "illuminate/support": "5.8.*", - "monolog/monolog": "^1.12", - "php": "^7.1.3" + "illuminate/contracts": "^8.0", + "illuminate/support": "^8.0", + "monolog/monolog": "^2.0", + "php": "^7.3|^8.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.8-dev" + "dev-master": "8.x-dev" } }, "autoload": { @@ -1743,32 +2171,79 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2019-09-03T12:41:07+00:00" + "time": "2021-11-17T15:04:30+00:00" }, { - "name": "illuminate/pagination", - "version": "v5.8.36", + "name": "illuminate/macroable", + "version": "v8.77.1", "source": { "type": "git", - "url": "https://github.com/illuminate/pagination.git", - "reference": "391134bc87a47b3dfe5cf60df73e5e0080aec220" + "url": "https://github.com/illuminate/macroable.git", + "reference": "aed81891a6e046fdee72edd497f822190f61c162" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/pagination/zipball/391134bc87a47b3dfe5cf60df73e5e0080aec220", - "reference": "391134bc87a47b3dfe5cf60df73e5e0080aec220", + "url": "https://api.github.com/repos/illuminate/macroable/zipball/aed81891a6e046fdee72edd497f822190f61c162", + "reference": "aed81891a6e046fdee72edd497f822190f61c162", "shasum": "" }, "require": { - "ext-json": "*", - "illuminate/contracts": "5.8.*", - "illuminate/support": "5.8.*", - "php": "^7.1.3" + "php": "^7.3|^8.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.8-dev" + "dev-master": "8.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Macroable package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2021-11-16T13:57:03+00:00" + }, + { + "name": "illuminate/pagination", + "version": "v8.77.1", + "source": { + "type": "git", + "url": "https://github.com/illuminate/pagination.git", + "reference": "d631f65e67f1b575642f9faaff54ac6d42791e80" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/pagination/zipball/d631f65e67f1b575642f9faaff54ac6d42791e80", + "reference": "d631f65e67f1b575642f9faaff54ac6d42791e80", + "shasum": "" + }, + "require": { + "ext-json": "*", + "illuminate/collections": "^8.0", + "illuminate/contracts": "^8.0", + "illuminate/support": "^8.0", + "php": "^7.3|^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "8.x-dev" } }, "autoload": { @@ -1792,31 +2267,31 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2019-03-18T14:45:00+00:00" + "time": "2021-12-15T11:14:57+00:00" }, { "name": "illuminate/pipeline", - "version": "v5.8.36", + "version": "v8.77.1", "source": { "type": "git", "url": "https://github.com/illuminate/pipeline.git", - "reference": "9e81b335d853ddd633a86a7f7e3fceed3b14f3d7" + "reference": "23aeff5b26ae4aee3f370835c76bd0f4e93f71d2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/pipeline/zipball/9e81b335d853ddd633a86a7f7e3fceed3b14f3d7", - "reference": "9e81b335d853ddd633a86a7f7e3fceed3b14f3d7", + "url": "https://api.github.com/repos/illuminate/pipeline/zipball/23aeff5b26ae4aee3f370835c76bd0f4e93f71d2", + "reference": "23aeff5b26ae4aee3f370835c76bd0f4e93f71d2", "shasum": "" }, "require": { - "illuminate/contracts": "5.8.*", - "illuminate/support": "5.8.*", - "php": "^7.1.3" + "illuminate/contracts": "^8.0", + "illuminate/support": "^8.0", + "php": "^7.3|^8.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.8-dev" + "dev-master": "8.x-dev" } }, "autoload": { @@ -1840,46 +2315,49 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2019-02-25T10:08:47+00:00" + "time": "2021-03-26T18:39:16+00:00" }, { "name": "illuminate/queue", - "version": "v5.8.36", + "version": "v8.77.1", "source": { "type": "git", "url": "https://github.com/illuminate/queue.git", - "reference": "36559f77916c16643bc614765db1e840d7bd9a00" + "reference": "7111e907b4febe419c512a27ca4ce0510638308d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/queue/zipball/36559f77916c16643bc614765db1e840d7bd9a00", - "reference": "36559f77916c16643bc614765db1e840d7bd9a00", + "url": "https://api.github.com/repos/illuminate/queue/zipball/7111e907b4febe419c512a27ca4ce0510638308d", + "reference": "7111e907b4febe419c512a27ca4ce0510638308d", "shasum": "" }, "require": { "ext-json": "*", - "illuminate/console": "5.8.*", - "illuminate/container": "5.8.*", - "illuminate/contracts": "5.8.*", - "illuminate/database": "5.8.*", - "illuminate/filesystem": "5.8.*", - "illuminate/support": "5.8.*", - "opis/closure": "^3.1", - "php": "^7.1.3", - "symfony/debug": "^4.2", - "symfony/process": "^4.2" + "illuminate/collections": "^8.0", + "illuminate/console": "^8.0", + "illuminate/container": "^8.0", + "illuminate/contracts": "^8.0", + "illuminate/database": "^8.0", + "illuminate/filesystem": "^8.0", + "illuminate/pipeline": "^8.0", + "illuminate/support": "^8.0", + "laravel/serializable-closure": "^1.0", + "opis/closure": "^3.6", + "php": "^7.3|^8.0", + "ramsey/uuid": "^4.2.2", + "symfony/process": "^5.4" }, "suggest": { - "aws/aws-sdk-php": "Required to use the SQS queue driver (^3.0).", + "aws/aws-sdk-php": "Required to use the SQS queue driver and DynamoDb failed job storage (^3.198.1).", "ext-pcntl": "Required to use all features of the queue worker.", "ext-posix": "Required to use all features of the queue worker.", - "illuminate/redis": "Required to use the Redis queue driver (5.8.*).", + "illuminate/redis": "Required to use the Redis queue driver (^8.0).", "pda/pheanstalk": "Required to use the Beanstalk queue driver (^4.0)." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.8-dev" + "dev-master": "8.x-dev" } }, "autoload": { @@ -1903,32 +2381,37 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2019-08-26T03:26:42+00:00" + "time": "2021-11-30T14:13:40+00:00" }, { "name": "illuminate/redis", - "version": "v5.8.36", + "version": "v8.77.1", "source": { "type": "git", "url": "https://github.com/illuminate/redis.git", - "reference": "59f47da5c12a5d808582b2c1c74b2912a4e2176e" + "reference": "513618b78fb42f440f7717ba05b28350f7edd284" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/redis/zipball/59f47da5c12a5d808582b2c1c74b2912a4e2176e", - "reference": "59f47da5c12a5d808582b2c1c74b2912a4e2176e", + "url": "https://api.github.com/repos/illuminate/redis/zipball/513618b78fb42f440f7717ba05b28350f7edd284", + "reference": "513618b78fb42f440f7717ba05b28350f7edd284", "shasum": "" }, "require": { - "illuminate/contracts": "5.8.*", - "illuminate/support": "5.8.*", - "php": "^7.1.3", - "predis/predis": "^1.0" + "illuminate/collections": "^8.0", + "illuminate/contracts": "^8.0", + "illuminate/macroable": "^8.0", + "illuminate/support": "^8.0", + "php": "^7.3|^8.0" + }, + "suggest": { + "ext-redis": "Required to use the phpredis connector (^4.0|^5.0).", + "predis/predis": "Required to use the predis connector (^1.1.9)." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.8-dev" + "dev-master": "8.x-dev" } }, "autoload": { @@ -1952,38 +2435,39 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2019-08-31T16:59:52+00:00" + "time": "2021-12-17T20:07:09+00:00" }, { "name": "illuminate/session", - "version": "v5.8.36", + "version": "v8.77.1", "source": { "type": "git", "url": "https://github.com/illuminate/session.git", - "reference": "087d360f7b9d75bc964280b890c2f2fe8efaf71f" + "reference": "c5964a2348354f266fb18aac60eed0b81355c258" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/session/zipball/087d360f7b9d75bc964280b890c2f2fe8efaf71f", - "reference": "087d360f7b9d75bc964280b890c2f2fe8efaf71f", + "url": "https://api.github.com/repos/illuminate/session/zipball/c5964a2348354f266fb18aac60eed0b81355c258", + "reference": "c5964a2348354f266fb18aac60eed0b81355c258", "shasum": "" }, "require": { "ext-json": "*", - "illuminate/contracts": "5.8.*", - "illuminate/filesystem": "5.8.*", - "illuminate/support": "5.8.*", - "php": "^7.1.3", - "symfony/finder": "^4.2", - "symfony/http-foundation": "^4.2" + "illuminate/collections": "^8.0", + "illuminate/contracts": "^8.0", + "illuminate/filesystem": "^8.0", + "illuminate/support": "^8.0", + "php": "^7.3|^8.0", + "symfony/finder": "^5.4", + "symfony/http-foundation": "^5.4" }, "suggest": { - "illuminate/console": "Required to use the session:table command (5.8.*)." + "illuminate/console": "Required to use the session:table command (^8.0)." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.8-dev" + "dev-master": "8.x-dev" } }, "autoload": { @@ -2007,45 +2491,48 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2019-07-08T13:48:55+00:00" + "time": "2021-11-30T14:13:40+00:00" }, { "name": "illuminate/support", - "version": "v5.8.36", + "version": "v8.77.1", "source": { "type": "git", "url": "https://github.com/illuminate/support.git", - "reference": "df4af6a32908f1d89d74348624b57e3233eea247" + "reference": "789b5c9a28884bc6b07841574cc86abdec7c5f68" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/support/zipball/df4af6a32908f1d89d74348624b57e3233eea247", - "reference": "df4af6a32908f1d89d74348624b57e3233eea247", + "url": "https://api.github.com/repos/illuminate/support/zipball/789b5c9a28884bc6b07841574cc86abdec7c5f68", + "reference": "789b5c9a28884bc6b07841574cc86abdec7c5f68", "shasum": "" }, "require": { - "doctrine/inflector": "^1.1", + "doctrine/inflector": "^1.4|^2.0", "ext-json": "*", "ext-mbstring": "*", - "illuminate/contracts": "5.8.*", - "nesbot/carbon": "^1.26.3 || ^2.0", - "php": "^7.1.3" + "illuminate/collections": "^8.0", + "illuminate/contracts": "^8.0", + "illuminate/macroable": "^8.0", + "nesbot/carbon": "^2.53.1", + "php": "^7.3|^8.0", + "voku/portable-ascii": "^1.4.8" }, "conflict": { "tightenco/collect": "<5.5.33" }, "suggest": { - "illuminate/filesystem": "Required to use the composer class (5.8.*).", - "moontoast/math": "Required to use ordered UUIDs (^1.1).", - "ramsey/uuid": "Required to use Str::uuid() (^3.7).", - "symfony/process": "Required to use the composer class (^4.2).", - "symfony/var-dumper": "Required to use the dd function (^4.2).", - "vlucas/phpdotenv": "Required to use the env helper (^3.3)." + "illuminate/filesystem": "Required to use the composer class (^8.0).", + "league/commonmark": "Required to use Str::markdown() and Stringable::markdown() (^1.3|^2.0.2).", + "ramsey/uuid": "Required to use Str::uuid() (^4.2.2).", + "symfony/process": "Required to use the composer class (^5.4).", + "symfony/var-dumper": "Required to use the dd function (^5.4).", + "vlucas/phpdotenv": "Required to use the Env class and env helper (^5.2)." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.8-dev" + "dev-master": "8.x-dev" } }, "autoload": { @@ -2072,33 +2559,93 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2019-12-12T14:16:47+00:00" + "time": "2021-12-21T14:59:41+00:00" }, { - "name": "illuminate/translation", - "version": "v5.8.36", + "name": "illuminate/testing", + "version": "v8.77.1", "source": { "type": "git", - "url": "https://github.com/illuminate/translation.git", - "reference": "a23986a9ae77013046426bbeb4fe9a29e2527f76" + "url": "https://github.com/illuminate/testing.git", + "reference": "feca7bc8f4de97434e3923ae7b09c5c047d46038" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/translation/zipball/a23986a9ae77013046426bbeb4fe9a29e2527f76", - "reference": "a23986a9ae77013046426bbeb4fe9a29e2527f76", + "url": "https://api.github.com/repos/illuminate/testing/zipball/feca7bc8f4de97434e3923ae7b09c5c047d46038", + "reference": "feca7bc8f4de97434e3923ae7b09c5c047d46038", "shasum": "" }, "require": { - "ext-json": "*", - "illuminate/contracts": "5.8.*", - "illuminate/filesystem": "5.8.*", - "illuminate/support": "5.8.*", - "php": "^7.1.3" + "illuminate/collections": "^8.0", + "illuminate/contracts": "^8.0", + "illuminate/macroable": "^8.0", + "illuminate/support": "^8.0", + "php": "^7.3|^8.0" + }, + "suggest": { + "brianium/paratest": "Required to run tests in parallel (^6.0).", + "illuminate/console": "Required to assert console commands (^8.0).", + "illuminate/database": "Required to assert databases (^8.0).", + "illuminate/http": "Required to assert responses (^8.0).", + "mockery/mockery": "Required to use mocking (^1.4.4).", + "phpunit/phpunit": "Required to use assertions and run tests (^8.5.19|^9.5.8)." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.8-dev" + "dev-master": "8.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Testing\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Testing package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2021-12-01T12:58:42+00:00" + }, + { + "name": "illuminate/translation", + "version": "v8.77.1", + "source": { + "type": "git", + "url": "https://github.com/illuminate/translation.git", + "reference": "c10a68f37f590dc8c1c1fe5b6ad3f09381282137" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/translation/zipball/c10a68f37f590dc8c1c1fe5b6ad3f09381282137", + "reference": "c10a68f37f590dc8c1c1fe5b6ad3f09381282137", + "shasum": "" + }, + "require": { + "ext-json": "*", + "illuminate/collections": "^8.0", + "illuminate/contracts": "^8.0", + "illuminate/filesystem": "^8.0", + "illuminate/macroable": "^8.0", + "illuminate/support": "^8.0", + "php": "^7.3|^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "8.x-dev" } }, "autoload": { @@ -2122,39 +2669,43 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2019-06-04T14:33:55+00:00" + "time": "2021-10-30T16:01:33+00:00" }, { "name": "illuminate/validation", - "version": "v5.8.36", + "version": "v8.77.1", "source": { "type": "git", "url": "https://github.com/illuminate/validation.git", - "reference": "74e13a98299bbc3c48c5131a9239b9ad499a4efe" + "reference": "e5530f7c88d620848205cb17afc105520ac37be1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/validation/zipball/74e13a98299bbc3c48c5131a9239b9ad499a4efe", - "reference": "74e13a98299bbc3c48c5131a9239b9ad499a4efe", + "url": "https://api.github.com/repos/illuminate/validation/zipball/e5530f7c88d620848205cb17afc105520ac37be1", + "reference": "e5530f7c88d620848205cb17afc105520ac37be1", "shasum": "" }, "require": { - "egulias/email-validator": "^2.0", + "egulias/email-validator": "^2.1.10", "ext-json": "*", - "illuminate/container": "5.8.*", - "illuminate/contracts": "5.8.*", - "illuminate/support": "5.8.*", - "illuminate/translation": "5.8.*", - "php": "^7.1.3", - "symfony/http-foundation": "^4.2" + "illuminate/collections": "^8.0", + "illuminate/container": "^8.0", + "illuminate/contracts": "^8.0", + "illuminate/macroable": "^8.0", + "illuminate/support": "^8.0", + "illuminate/translation": "^8.0", + "php": "^7.3|^8.0", + "symfony/http-foundation": "^5.4", + "symfony/mime": "^5.4" }, "suggest": { - "illuminate/database": "Required to use the database presence verifier (5.8.*)." + "ext-bcmath": "Required to use the multiple_of validation rule.", + "illuminate/database": "Required to use the database presence verifier (^8.0)." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.8-dev" + "dev-master": "8.x-dev" } }, "autoload": { @@ -2178,36 +2729,37 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2019-10-24T16:01:23+00:00" + "time": "2021-12-20T15:12:15+00:00" }, { "name": "illuminate/view", - "version": "v5.8.36", + "version": "v8.77.1", "source": { "type": "git", "url": "https://github.com/illuminate/view.git", - "reference": "c859919bc3be97a3f114377d5d812f047b8ea90d" + "reference": "ec853721a70c11d51c6162a58410abb319a073b1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/view/zipball/c859919bc3be97a3f114377d5d812f047b8ea90d", - "reference": "c859919bc3be97a3f114377d5d812f047b8ea90d", + "url": "https://api.github.com/repos/illuminate/view/zipball/ec853721a70c11d51c6162a58410abb319a073b1", + "reference": "ec853721a70c11d51c6162a58410abb319a073b1", "shasum": "" }, "require": { "ext-json": "*", - "illuminate/container": "5.8.*", - "illuminate/contracts": "5.8.*", - "illuminate/events": "5.8.*", - "illuminate/filesystem": "5.8.*", - "illuminate/support": "5.8.*", - "php": "^7.1.3", - "symfony/debug": "^4.2" + "illuminate/collections": "^8.0", + "illuminate/container": "^8.0", + "illuminate/contracts": "^8.0", + "illuminate/events": "^8.0", + "illuminate/filesystem": "^8.0", + "illuminate/macroable": "^8.0", + "illuminate/support": "^8.0", + "php": "^7.3|^8.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.8-dev" + "dev-master": "8.x-dev" } }, "autoload": { @@ -2231,85 +2783,42 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2019-06-20T13:13:59+00:00" + "time": "2021-12-16T19:23:48+00:00" }, { - "name": "jakub-onderka/php-console-color", - "version": "v0.2", + "name": "jean85/pretty-package-versions", + "version": "2.0.5", "source": { "type": "git", - "url": "https://github.com/JakubOnderka/PHP-Console-Color.git", - "reference": "d5deaecff52a0d61ccb613bb3804088da0307191" + "url": "https://github.com/Jean85/pretty-package-versions.git", + "reference": "ae547e455a3d8babd07b96966b17d7fd21d9c6af" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/JakubOnderka/PHP-Console-Color/zipball/d5deaecff52a0d61ccb613bb3804088da0307191", - "reference": "d5deaecff52a0d61ccb613bb3804088da0307191", + "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/ae547e455a3d8babd07b96966b17d7fd21d9c6af", + "reference": "ae547e455a3d8babd07b96966b17d7fd21d9c6af", "shasum": "" }, "require": { - "php": ">=5.4.0" + "composer-runtime-api": "^2.0.0", + "php": "^7.1|^8.0" }, "require-dev": { - "jakub-onderka/php-code-style": "1.0", - "jakub-onderka/php-parallel-lint": "1.0", - "jakub-onderka/php-var-dump-check": "0.*", - "phpunit/phpunit": "~4.3", - "squizlabs/php_codesniffer": "1.*" + "friendsofphp/php-cs-fixer": "^2.17", + "jean85/composer-provided-replaced-stub-package": "^1.0", + "phpstan/phpstan": "^0.12.66", + "phpunit/phpunit": "^7.5|^8.5|^9.4", + "vimeo/psalm": "^4.3" }, "type": "library", - "autoload": { - "psr-4": { - "JakubOnderka\\PhpConsoleColor\\": "src/" + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" } }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-2-Clause" - ], - "authors": [ - { - "name": "Jakub Onderka", - "email": "jakub.onderka@gmail.com" - } - ], - "support": { - "issues": "https://github.com/JakubOnderka/PHP-Console-Color/issues", - "source": "https://github.com/JakubOnderka/PHP-Console-Color/tree/master" - }, - "abandoned": "php-parallel-lint/php-console-color", - "time": "2018-09-29T17:23:10+00:00" - }, - { - "name": "jakub-onderka/php-console-highlighter", - "version": "v0.4", - "source": { - "type": "git", - "url": "https://github.com/JakubOnderka/PHP-Console-Highlighter.git", - "reference": "9f7a229a69d52506914b4bc61bfdb199d90c5547" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/JakubOnderka/PHP-Console-Highlighter/zipball/9f7a229a69d52506914b4bc61bfdb199d90c5547", - "reference": "9f7a229a69d52506914b4bc61bfdb199d90c5547", - "shasum": "" - }, - "require": { - "ext-tokenizer": "*", - "jakub-onderka/php-console-color": "~0.2", - "php": ">=5.4.0" - }, - "require-dev": { - "jakub-onderka/php-code-style": "~1.0", - "jakub-onderka/php-parallel-lint": "~1.0", - "jakub-onderka/php-var-dump-check": "~0.1", - "phpunit/phpunit": "~4.0", - "squizlabs/php_codesniffer": "~1.5" - }, - "type": "library", "autoload": { "psr-4": { - "JakubOnderka\\PhpConsoleHighlighter\\": "src/" + "Jean85\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -2318,47 +2827,133 @@ ], "authors": [ { - "name": "Jakub Onderka", - "email": "acci@acci.cz", - "homepage": "http://www.acci.cz/" + "name": "Alessandro Lai", + "email": "alessandro.lai85@gmail.com" } ], - "description": "Highlight PHP code in terminal", + "description": "A library to get pretty versions strings of installed dependencies", + "keywords": [ + "composer", + "package", + "release", + "versions" + ], "support": { - "issues": "https://github.com/JakubOnderka/PHP-Console-Highlighter/issues", - "source": "https://github.com/JakubOnderka/PHP-Console-Highlighter/tree/master" + "issues": "https://github.com/Jean85/pretty-package-versions/issues", + "source": "https://github.com/Jean85/pretty-package-versions/tree/2.0.5" }, - "abandoned": "php-parallel-lint/php-console-highlighter", - "time": "2018-09-29T18:48:56+00:00" + "time": "2021-10-08T21:21:46+00:00" }, { - "name": "jikan-me/jikan", - "version": "v2.18.1", + "name": "jenssegers/mongodb", + "version": "3.8.4", "source": { "type": "git", - "url": "https://github.com/jikan-me/jikan.git", - "reference": "67d1e68f9aa9b9aa0eaedf6e094a481bb7812fab" + "url": "https://github.com/jenssegers/laravel-mongodb.git", + "reference": "6aa6ad12b3b52eeab1d090f282c14123ffad1dc9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jikan-me/jikan/zipball/67d1e68f9aa9b9aa0eaedf6e094a481bb7812fab", - "reference": "67d1e68f9aa9b9aa0eaedf6e094a481bb7812fab", + "url": "https://api.github.com/repos/jenssegers/laravel-mongodb/zipball/6aa6ad12b3b52eeab1d090f282c14123ffad1dc9", + "reference": "6aa6ad12b3b52eeab1d090f282c14123ffad1dc9", + "shasum": "" + }, + "require": { + "illuminate/container": "^8.0", + "illuminate/database": "^8.0", + "illuminate/events": "^8.0", + "illuminate/support": "^8.0", + "mongodb/mongodb": "^1.6" + }, + "require-dev": { + "doctrine/dbal": "^2.6", + "mockery/mockery": "^1.3.1", + "orchestra/testbench": "^6.0", + "phpunit/phpunit": "^9.0" + }, + "suggest": { + "jenssegers/mongodb-sentry": "Add Sentry support to Laravel-MongoDB", + "jenssegers/mongodb-session": "Add MongoDB session support to Laravel-MongoDB" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Jenssegers\\Mongodb\\MongodbServiceProvider", + "Jenssegers\\Mongodb\\MongodbQueueServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Jenssegers\\Mongodb\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jens Segers", + "homepage": "https://jenssegers.com" + } + ], + "description": "A MongoDB based Eloquent model and Query builder for Laravel (Moloquent)", + "homepage": "https://github.com/jenssegers/laravel-mongodb", + "keywords": [ + "database", + "eloquent", + "laravel", + "model", + "moloquent", + "mongo", + "mongodb" + ], + "support": { + "issues": "https://github.com/jenssegers/laravel-mongodb/issues", + "source": "https://github.com/jenssegers/laravel-mongodb/tree/3.8.4" + }, + "funding": [ + { + "url": "https://github.com/jenssegers", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/jenssegers/mongodb", + "type": "tidelift" + } + ], + "time": "2021-05-27T06:52:51+00:00" + }, + { + "name": "jikan-me/jikan", + "version": "3.0.0.x-dev", + "source": { + "type": "git", + "url": "https://github.com/jikan-me/jikan.git", + "reference": "70ea41605c5437c6f4b6205e75099b26f78dc77d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jikan-me/jikan/zipball/70ea41605c5437c6f4b6205e75099b26f78dc77d", + "reference": "70ea41605c5437c6f4b6205e75099b26f78dc77d", "shasum": "" }, "require": { "ext-json": "*", - "fabpot/goutte": "^3.2", - "php": "^7.1" + "fabpot/goutte": "4.0.1", + "php": "^7.4|^8.0" }, "require-dev": { - "brianium/paratest": "~4.0", + "brianium/paratest": "^6.4.1", "doctrine/collections": "^1.5", "friendsofphp/php-cs-fixer": "^2.16", "jakub-onderka/php-parallel-lint": "^1.0", "jikan-me/jikan-fixtures": "dev-master", - "php-vcr/php-vcr": "~1.4", + "php-vcr/php-vcr": "^1.6", "php-vcr/phpunit-testlistener-vcr": "~3.2", - "phpro/grumphp": "^0.19", + "phpro/grumphp": "^1.7.0", "phpunit/phpunit": "~9.0", "squizlabs/php_codesniffer": "^3.3" }, @@ -2383,7 +2978,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/v2.18.1" + "source": "https://github.com/jikan-me/jikan/tree/3.0.0" }, "funding": [ { @@ -2391,37 +2986,42 @@ "type": "patreon" } ], - "time": "2021-09-18T14:39:04+00:00" + "time": "2021-12-28T19:59:27+00:00" }, { "name": "jms/metadata", - "version": "1.7.0", + "version": "2.6.1", "source": { "type": "git", "url": "https://github.com/schmittjoh/metadata.git", - "reference": "e5854ab1aa643623dc64adde718a8eec32b957a8" + "reference": "c3a3214354b5a765a19875f7b7c5ebcd94e462e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/metadata/zipball/e5854ab1aa643623dc64adde718a8eec32b957a8", - "reference": "e5854ab1aa643623dc64adde718a8eec32b957a8", + "url": "https://api.github.com/repos/schmittjoh/metadata/zipball/c3a3214354b5a765a19875f7b7c5ebcd94e462e5", + "reference": "c3a3214354b5a765a19875f7b7c5ebcd94e462e5", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": "^7.2|^8.0" }, "require-dev": { - "doctrine/cache": "~1.0", - "symfony/cache": "~3.1" + "doctrine/cache": "^1.0", + "doctrine/coding-standard": "^8.0", + "mikey179/vfsstream": "^1.6.7", + "phpunit/phpunit": "^8.5|^9.0", + "psr/container": "^1.0", + "symfony/cache": "^3.1|^4.0|^5.0", + "symfony/dependency-injection": "^3.1|^4.0|^5.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.5.x-dev" + "dev-master": "2.x-dev" } }, "autoload": { - "psr-0": { + "psr-4": { "Metadata\\": "src/" } }, @@ -2430,13 +3030,13 @@ "MIT" ], "authors": [ - { - "name": "Asmir Mustafic", - "email": "goetas@gmail.com" - }, { "name": "Johannes M. Schmitt", "email": "schmittjoh@gmail.com" + }, + { + "name": "Asmir Mustafic", + "email": "goetas@gmail.com" } ], "description": "Class/method/property metadata management in PHP", @@ -2448,106 +3048,67 @@ ], "support": { "issues": "https://github.com/schmittjoh/metadata/issues", - "source": "https://github.com/schmittjoh/metadata/tree/1.x" + "source": "https://github.com/schmittjoh/metadata/tree/2.6.1" }, - "time": "2018-10-26T12:40:10+00:00" - }, - { - "name": "jms/parser-lib", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/schmittjoh/parser-lib.git", - "reference": "c509473bc1b4866415627af0e1c6cc8ac97fa51d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/parser-lib/zipball/c509473bc1b4866415627af0e1c6cc8ac97fa51d", - "reference": "c509473bc1b4866415627af0e1c6cc8ac97fa51d", - "shasum": "" - }, - "require": { - "phpoption/phpoption": ">=0.9,<2.0-dev" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "psr-0": { - "JMS\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache2" - ], - "description": "A library for easily creating recursive-descent parsers.", - "support": { - "issues": "https://github.com/schmittjoh/parser-lib/issues", - "source": "https://github.com/schmittjoh/parser-lib/tree/1.0.0" - }, - "time": "2012-11-18T18:08:43+00:00" + "time": "2021-11-22T12:27:42+00:00" }, { "name": "jms/serializer", - "version": "1.14.1", + "version": "3.17.0", "source": { "type": "git", "url": "https://github.com/schmittjoh/serializer.git", - "reference": "ba908d278fff27ec01fb4349f372634ffcd697c0" + "reference": "6e17603abc16c5b6eed41f51844bc51dda51cf94" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/serializer/zipball/ba908d278fff27ec01fb4349f372634ffcd697c0", - "reference": "ba908d278fff27ec01fb4349f372634ffcd697c0", + "url": "https://api.github.com/repos/schmittjoh/serializer/zipball/6e17603abc16c5b6eed41f51844bc51dda51cf94", + "reference": "6e17603abc16c5b6eed41f51844bc51dda51cf94", "shasum": "" }, "require": { - "doctrine/annotations": "^1.0", + "doctrine/annotations": "^1.13", "doctrine/instantiator": "^1.0.3", - "jms/metadata": "^1.3", - "jms/parser-lib": "1.*", - "php": "^5.5|^7.0", - "phpcollection/phpcollection": "~0.1", - "phpoption/phpoption": "^1.1" - }, - "conflict": { - "twig/twig": "<1.12" + "doctrine/lexer": "^1.1", + "jms/metadata": "^2.6", + "php": "^7.2||^8.0", + "phpstan/phpdoc-parser": "^0.4 || ^0.5 || ^1.0" }, "require-dev": { + "doctrine/coding-standard": "^8.1", "doctrine/orm": "~2.1", + "doctrine/persistence": "^1.3.3|^2.0|^3.0", "doctrine/phpcr-odm": "^1.3|^2.0", "ext-pdo_sqlite": "*", "jackalope/jackalope-doctrine-dbal": "^1.1.5", - "phpunit/phpunit": "^4.8|^5.0", - "propel/propel1": "~1.7", + "ocramius/proxy-manager": "^1.0|^2.0", + "phpbench/phpbench": "^1.0", + "phpstan/phpstan": "^1.0.2", + "phpunit/phpunit": "^8.5.21||^9.0", "psr/container": "^1.0", - "symfony/dependency-injection": "^2.7|^3.3|^4.0", - "symfony/expression-language": "^2.6|^3.0", - "symfony/filesystem": "^2.1", - "symfony/form": "~2.1|^3.0", - "symfony/translation": "^2.1|^3.0", - "symfony/validator": "^2.2|^3.0", - "symfony/yaml": "^2.1|^3.0", - "twig/twig": "~1.12|~2.0" + "symfony/dependency-injection": "^3.0|^4.0|^5.0|^6.0", + "symfony/expression-language": "^3.2|^4.0|^5.0|^6.0", + "symfony/filesystem": "^3.0|^4.0|^5.0|^6.0", + "symfony/form": "^3.0|^4.0|^5.0|^6.0", + "symfony/translation": "^3.0|^4.0|^5.0|^6.0", + "symfony/validator": "^3.1.9|^4.0|^5.0|^6.0", + "symfony/yaml": "^3.3|^4.0|^5.0|^6.0", + "twig/twig": "~1.34|~2.4|^3.0" }, "suggest": { - "doctrine/cache": "Required if you like to use cache functionality.", "doctrine/collections": "Required if you like to use doctrine collection types as ArrayCollection.", - "symfony/yaml": "Required if you'd like to serialize data to YAML format." + "symfony/cache": "Required if you like to use cache functionality.", + "symfony/yaml": "Required if you'd like to use the YAML metadata format." }, "type": "library", "extra": { "branch-alias": { - "dev-1.x": "1.14-dev" + "dev-master": "3.x-dev" } }, "autoload": { - "psr-0": { - "JMS\\Serializer": "src/" + "psr-4": { + "JMS\\Serializer\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -2575,65 +3136,136 @@ ], "support": { "issues": "https://github.com/schmittjoh/serializer/issues", - "source": "https://github.com/schmittjoh/serializer/tree/1.14.1" + "source": "https://github.com/schmittjoh/serializer/tree/3.17.0" }, - "time": "2020-02-22T20:59:37+00:00" + "funding": [ + { + "url": "https://github.com/goetas", + "type": "github" + } + ], + "time": "2021-12-14T15:01:05+00:00" }, { - "name": "laravel/lumen-framework", - "version": "v5.8.13", + "name": "laravel/legacy-factories", + "version": "v1.1.1", "source": { "type": "git", - "url": "https://github.com/laravel/lumen-framework.git", - "reference": "5d1d1ba8dbc5b69a17370d276e96d30eb00a2b48" + "url": "https://github.com/laravel/legacy-factories.git", + "reference": "8091d6d64e0e6ea22fb3326ef0b21936d0a0217c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/lumen-framework/zipball/5d1d1ba8dbc5b69a17370d276e96d30eb00a2b48", - "reference": "5d1d1ba8dbc5b69a17370d276e96d30eb00a2b48", + "url": "https://api.github.com/repos/laravel/legacy-factories/zipball/8091d6d64e0e6ea22fb3326ef0b21936d0a0217c", + "reference": "8091d6d64e0e6ea22fb3326ef0b21936d0a0217c", "shasum": "" }, "require": { - "dragonmantank/cron-expression": "^2.0", - "illuminate/auth": "5.8.*", - "illuminate/broadcasting": "5.8.*", - "illuminate/bus": "5.8.*", - "illuminate/cache": "5.8.*", - "illuminate/config": "5.8.*", - "illuminate/container": "5.8.*", - "illuminate/contracts": "5.8.*", - "illuminate/database": "5.8.*", - "illuminate/encryption": "5.8.*", - "illuminate/events": "5.8.*", - "illuminate/filesystem": "5.8.*", - "illuminate/hashing": "5.8.*", - "illuminate/http": "5.8.*", - "illuminate/log": "5.8.*", - "illuminate/pagination": "5.8.*", - "illuminate/pipeline": "5.8.*", - "illuminate/queue": "5.8.*", - "illuminate/support": "5.8.*", - "illuminate/translation": "5.8.*", - "illuminate/validation": "5.8.*", - "illuminate/view": "5.8.*", - "nikic/fast-route": "^1.3", - "php": "^7.1.3", - "symfony/http-foundation": "^4.2", - "symfony/http-kernel": "^4.2", - "symfony/var-dumper": "^4.2", - "vlucas/phpdotenv": "^3.3" - }, - "require-dev": { - "mockery/mockery": "^1.0", - "phpunit/phpunit": "^7.0|^8.0" - }, - "suggest": { - "laravel/tinker": "Required to use the tinker console command (^1.0)." + "illuminate/macroable": "^8.0", + "php": "^7.3|^8.0", + "symfony/finder": "^3.4|^4.0|^5.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.8-dev" + "dev-master": "1.x-dev" + }, + "laravel": { + "providers": [ + "Illuminate\\Database\\Eloquent\\LegacyFactoryServiceProvider" + ] + } + }, + "autoload": { + "files": [ + "helpers.php" + ], + "psr-4": { + "Illuminate\\Database\\Eloquent\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The legacy version of the Laravel Eloquent factories.", + "homepage": "http://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2021-10-19T13:10:37+00:00" + }, + { + "name": "laravel/lumen-framework", + "version": "v8.3.4", + "source": { + "type": "git", + "url": "https://github.com/laravel/lumen-framework.git", + "reference": "733d1199d3344be337743f11df31b4048ec7fd1c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/lumen-framework/zipball/733d1199d3344be337743f11df31b4048ec7fd1c", + "reference": "733d1199d3344be337743f11df31b4048ec7fd1c", + "shasum": "" + }, + "require": { + "dragonmantank/cron-expression": "^3.0.2", + "illuminate/auth": "^8.65", + "illuminate/broadcasting": "^8.65", + "illuminate/bus": "^8.65", + "illuminate/cache": "^8.65", + "illuminate/collections": "^8.65", + "illuminate/config": "^8.65", + "illuminate/console": "^8.65", + "illuminate/container": "^8.65", + "illuminate/contracts": "^8.65", + "illuminate/database": "^8.65", + "illuminate/encryption": "^8.65", + "illuminate/events": "^8.65", + "illuminate/filesystem": "^8.65", + "illuminate/hashing": "^8.65", + "illuminate/http": "^8.65", + "illuminate/log": "^8.65", + "illuminate/macroable": "^8.65", + "illuminate/pagination": "^8.65", + "illuminate/pipeline": "^8.65", + "illuminate/queue": "^8.65", + "illuminate/support": "^8.65", + "illuminate/testing": "^8.65", + "illuminate/translation": "^8.65", + "illuminate/validation": "^8.65", + "illuminate/view": "^8.65", + "nikic/fast-route": "^1.3", + "php": "^7.3|^8.0", + "symfony/console": "^5.4", + "symfony/error-handler": "^5.4", + "symfony/http-foundation": "^5.4", + "symfony/http-kernel": "^5.4", + "symfony/mime": "^5.4", + "symfony/var-dumper": "^5.4", + "vlucas/phpdotenv": "^5.2" + }, + "require-dev": { + "mockery/mockery": "^1.4.4", + "phpunit/phpunit": "^8.5.19|^9.5.8" + }, + "suggest": { + "laravel/tinker": "Required to use the tinker console command (^2.0).", + "nyholm/psr7": "Required to use PSR-7 bridging features (^1.2).", + "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^2.0)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "8.x-dev" } }, "autoload": { @@ -2651,7 +3283,7 @@ "authors": [ { "name": "Taylor Otwell", - "email": "taylorotwell@gmail.com" + "email": "taylor@laravel.com" } ], "description": "The Laravel Lumen Framework.", @@ -2665,55 +3297,346 @@ "issues": "https://github.com/laravel/lumen-framework/issues", "source": "https://github.com/laravel/lumen-framework" }, - "time": "2019-08-28T21:21:05+00:00" + "time": "2021-12-22T10:11:35+00:00" }, { - "name": "monolog/monolog", - "version": "1.26.1", + "name": "laravel/serializable-closure", + "version": "v1.0.5", "source": { "type": "git", - "url": "https://github.com/Seldaek/monolog.git", - "reference": "c6b00f05152ae2c9b04a448f99c7590beb6042f5" + "url": "https://github.com/laravel/serializable-closure.git", + "reference": "25de3be1bca1b17d52ff0dc02b646c667ac7266c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/c6b00f05152ae2c9b04a448f99c7590beb6042f5", - "reference": "c6b00f05152ae2c9b04a448f99c7590beb6042f5", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/25de3be1bca1b17d52ff0dc02b646c667ac7266c", + "reference": "25de3be1bca1b17d52ff0dc02b646c667ac7266c", "shasum": "" }, "require": { - "php": ">=5.3.0", - "psr/log": "~1.0" + "php": "^7.3|^8.0" + }, + "require-dev": { + "pestphp/pest": "^1.18", + "phpstan/phpstan": "^0.12.98", + "symfony/var-dumper": "^5.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laravel\\SerializableClosure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + }, + { + "name": "Nuno Maduro", + "email": "nuno@laravel.com" + } + ], + "description": "Laravel Serializable Closure provides an easy and secure way to serialize closures in PHP.", + "keywords": [ + "closure", + "laravel", + "serializable" + ], + "support": { + "issues": "https://github.com/laravel/serializable-closure/issues", + "source": "https://github.com/laravel/serializable-closure" + }, + "time": "2021-11-30T15:53:04+00:00" + }, + { + "name": "league/flysystem", + "version": "1.1.9", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem.git", + "reference": "094defdb4a7001845300334e7c1ee2335925ef99" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/094defdb4a7001845300334e7c1ee2335925ef99", + "reference": "094defdb4a7001845300334e7c1ee2335925ef99", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "league/mime-type-detection": "^1.3", + "php": "^7.2.5 || ^8.0" + }, + "conflict": { + "league/flysystem-sftp": "<1.0.6" + }, + "require-dev": { + "phpspec/prophecy": "^1.11.1", + "phpunit/phpunit": "^8.5.8" + }, + "suggest": { + "ext-ftp": "Allows you to use FTP server storage", + "ext-openssl": "Allows you to use FTPS server storage", + "league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2", + "league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3", + "league/flysystem-azure": "Allows you to use Windows Azure Blob storage", + "league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching", + "league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem", + "league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files", + "league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib", + "league/flysystem-webdav": "Allows you to use WebDAV storage", + "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter", + "spatie/flysystem-dropbox": "Allows you to use Dropbox storage", + "srmklive/flysystem-dropbox-v2": "Allows you to use Dropbox storage for PHP 5 applications" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Flysystem\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frenky.net" + } + ], + "description": "Filesystem abstraction: Many filesystems, one API.", + "keywords": [ + "Cloud Files", + "WebDAV", + "abstraction", + "aws", + "cloud", + "copy.com", + "dropbox", + "file systems", + "files", + "filesystem", + "filesystems", + "ftp", + "rackspace", + "remote", + "s3", + "sftp", + "storage" + ], + "support": { + "issues": "https://github.com/thephpleague/flysystem/issues", + "source": "https://github.com/thephpleague/flysystem/tree/1.1.9" + }, + "funding": [ + { + "url": "https://offset.earth/frankdejonge", + "type": "other" + } + ], + "time": "2021-12-09T09:40:50+00:00" + }, + { + "name": "league/mime-type-detection", + "version": "1.9.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/mime-type-detection.git", + "reference": "aa70e813a6ad3d1558fc927863d47309b4c23e69" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/aa70e813a6ad3d1558fc927863d47309b4c23e69", + "reference": "aa70e813a6ad3d1558fc927863d47309b4c23e69", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.2", + "phpstan/phpstan": "^0.12.68", + "phpunit/phpunit": "^8.5.8 || ^9.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\MimeTypeDetection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "Mime-type detection for Flysystem", + "support": { + "issues": "https://github.com/thephpleague/mime-type-detection/issues", + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.9.0" + }, + "funding": [ + { + "url": "https://github.com/frankdejonge", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/flysystem", + "type": "tidelift" + } + ], + "time": "2021-11-21T11:48:40+00:00" + }, + { + "name": "mongodb/mongodb", + "version": "1.10.1", + "source": { + "type": "git", + "url": "https://github.com/mongodb/mongo-php-library.git", + "reference": "9e0da590ec94e8af9a0ee065294627ffaee6244e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mongodb/mongo-php-library/zipball/9e0da590ec94e8af9a0ee065294627ffaee6244e", + "reference": "9e0da590ec94e8af9a0ee065294627ffaee6244e", + "shasum": "" + }, + "require": { + "ext-hash": "*", + "ext-json": "*", + "ext-mongodb": "^1.11.0", + "jean85/pretty-package-versions": "^1.2 || ^2.0.1", + "php": "^7.1 || ^8.0", + "symfony/polyfill-php80": "^1.19" + }, + "require-dev": { + "doctrine/coding-standard": "^9.0", + "squizlabs/php_codesniffer": "^3.6", + "symfony/phpunit-bridge": "^5.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10.x-dev" + } + }, + "autoload": { + "psr-4": { + "MongoDB\\": "src/" + }, + "files": [ + "src/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Andreas Braun", + "email": "andreas.braun@mongodb.com" + }, + { + "name": "Jeremy Mikola", + "email": "jmikola@gmail.com" + } + ], + "description": "MongoDB driver library", + "homepage": "https://jira.mongodb.org/browse/PHPLIB", + "keywords": [ + "database", + "driver", + "mongodb", + "persistence" + ], + "support": { + "issues": "https://github.com/mongodb/mongo-php-library/issues", + "source": "https://github.com/mongodb/mongo-php-library/tree/1.10.1" + }, + "time": "2021-12-06T21:42:33+00:00" + }, + { + "name": "monolog/monolog", + "version": "2.3.5", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "fd4380d6fc37626e2f799f29d91195040137eba9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/fd4380d6fc37626e2f799f29d91195040137eba9", + "reference": "fd4380d6fc37626e2f799f29d91195040137eba9", + "shasum": "" + }, + "require": { + "php": ">=7.2", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" }, "provide": { - "psr/log-implementation": "1.0.0" + "psr/log-implementation": "1.0.0 || 2.0.0 || 3.0.0" }, "require-dev": { "aws/aws-sdk-php": "^2.4.9 || ^3.0", "doctrine/couchdb": "~1.0@dev", - "graylog2/gelf-php": "~1.0", - "php-amqplib/php-amqplib": "~2.4", + "elasticsearch/elasticsearch": "^7", + "graylog2/gelf-php": "^1.4.2", + "mongodb/mongodb": "^1.8", + "php-amqplib/php-amqplib": "~2.4 || ^3", "php-console/php-console": "^3.1.3", - "phpstan/phpstan": "^0.12.59", - "phpunit/phpunit": "~4.5", - "ruflin/elastica": ">=0.90 <3.0", - "sentry/sentry": "^0.13", + "phpspec/prophecy": "^1.6.1", + "phpstan/phpstan": "^0.12.91", + "phpunit/phpunit": "^8.5", + "predis/predis": "^1.1", + "rollbar/rollbar": "^1.3", + "ruflin/elastica": ">=0.90@dev", "swiftmailer/swiftmailer": "^5.3|^6.0" }, "suggest": { "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", - "ext-mongo": "Allow sending log messages to a MongoDB server", + "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler", + "ext-mbstring": "Allow to work properly with unicode symbols", + "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", + "ext-openssl": "Required to send log messages using SSL", + "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)", "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", - "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", "php-console/php-console": "Allow sending log messages to Google Chrome", "rollbar/rollbar": "Allow sending log messages to Rollbar", - "ruflin/elastica": "Allow sending log messages to an Elastic Search server", - "sentry/sentry": "Allow sending log messages to a Sentry server" + "ruflin/elastica": "Allow sending log messages to an Elastic Search server" }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.x-dev" + } + }, "autoload": { "psr-4": { "Monolog\\": "src/Monolog" @@ -2727,11 +3650,11 @@ { "name": "Jordi Boggiano", "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" + "homepage": "https://seld.be" } ], "description": "Sends your logs to files, sockets, inboxes, databases and various web services", - "homepage": "http://github.com/Seldaek/monolog", + "homepage": "https://github.com/Seldaek/monolog", "keywords": [ "log", "logging", @@ -2739,7 +3662,7 @@ ], "support": { "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/1.26.1" + "source": "https://github.com/Seldaek/monolog/tree/2.3.5" }, "funding": [ { @@ -2751,20 +3674,20 @@ "type": "tidelift" } ], - "time": "2021-05-28T08:32:12+00:00" + "time": "2021-10-01T21:08:31+00:00" }, { "name": "nesbot/carbon", - "version": "2.53.1", + "version": "2.55.2", "source": { "type": "git", "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "f4655858a784988f880c1b8c7feabbf02dfdf045" + "reference": "8c2a18ce3e67c34efc1b29f64fe61304368259a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/f4655858a784988f880c1b8c7feabbf02dfdf045", - "reference": "f4655858a784988f880c1b8c7feabbf02dfdf045", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/8c2a18ce3e67c34efc1b29f64fe61304368259a2", + "reference": "8c2a18ce3e67c34efc1b29f64fe61304368259a2", "shasum": "" }, "require": { @@ -2772,9 +3695,10 @@ "php": "^7.1.8 || ^8.0", "symfony/polyfill-mbstring": "^1.0", "symfony/polyfill-php80": "^1.16", - "symfony/translation": "^3.4 || ^4.0 || ^5.0" + "symfony/translation": "^3.4 || ^4.0 || ^5.0 || ^6.0" }, "require-dev": { + "doctrine/dbal": "^2.0 || ^3.0", "doctrine/orm": "^2.7", "friendsofphp/php-cs-fixer": "^3.0", "kylekatarnls/multi-tester": "^2.0", @@ -2832,6 +3756,7 @@ "time" ], "support": { + "docs": "https://carbon.nesbot.com/docs", "issues": "https://github.com/briannesbitt/Carbon/issues", "source": "https://github.com/briannesbitt/Carbon" }, @@ -2845,7 +3770,7 @@ "type": "tidelift" } ], - "time": "2021-09-06T09:29:23+00:00" + "time": "2021-12-03T14:59:52+00:00" }, { "name": "nikic/fast-route", @@ -2899,24 +3824,25 @@ }, { "name": "nikic/php-parser", - "version": "v3.1.5", + "version": "v4.13.2", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "bb87e28e7d7b8d9a7fda231d37457c9210faf6ce" + "reference": "210577fe3cf7badcc5814d99455df46564f3c077" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/bb87e28e7d7b8d9a7fda231d37457c9210faf6ce", - "reference": "bb87e28e7d7b8d9a7fda231d37457c9210faf6ce", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/210577fe3cf7badcc5814d99455df46564f3c077", + "reference": "210577fe3cf7badcc5814d99455df46564f3c077", "shasum": "" }, "require": { "ext-tokenizer": "*", - "php": ">=5.5" + "php": ">=7.0" }, "require-dev": { - "phpunit/phpunit": "~4.0|~5.0" + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" }, "bin": [ "bin/php-parse" @@ -2924,7 +3850,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "4.9-dev" } }, "autoload": { @@ -2948,44 +3874,117 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v3.1.5" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.13.2" }, - "time": "2018-02-28T20:30:58+00:00" + "time": "2021-11-30T19:35:32+00:00" }, { - "name": "ocramius/package-versions", - "version": "1.11.0", + "name": "nyholm/psr7", + "version": "1.4.1", "source": { "type": "git", - "url": "https://github.com/Ocramius/PackageVersions.git", - "reference": "f51ff2b2b49baaa302d6bf71880e4d8b5acd7015" + "url": "https://github.com/Nyholm/psr7.git", + "reference": "2212385b47153ea71b1c1b1374f8cb5e4f7892ec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Ocramius/PackageVersions/zipball/f51ff2b2b49baaa302d6bf71880e4d8b5acd7015", - "reference": "f51ff2b2b49baaa302d6bf71880e4d8b5acd7015", + "url": "https://api.github.com/repos/Nyholm/psr7/zipball/2212385b47153ea71b1c1b1374f8cb5e4f7892ec", + "reference": "2212385b47153ea71b1c1b1374f8cb5e4f7892ec", "shasum": "" }, "require": { - "composer-plugin-api": "^2.0.0", - "composer-runtime-api": "^2.0.0", - "php": "^7.4.7" + "php": ">=7.1", + "php-http/message-factory": "^1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" }, "require-dev": { - "composer/composer": "^2.0.0@dev", - "doctrine/coding-standard": "^8.1.0", - "ext-zip": "^1.15.0", - "infection/infection": "^0.16.4", - "phpunit/phpunit": "^9.1.5", - "vimeo/psalm": "^3.12.2" + "http-interop/http-factory-tests": "^0.9", + "php-http/psr7-integration-tests": "^1.0", + "phpunit/phpunit": "^7.5 || 8.5 || 9.4", + "symfony/error-handler": "^4.4" }, - "type": "composer-plugin", + "type": "library", "extra": { - "class": "PackageVersions\\Installer", "branch-alias": { - "dev-master": "1.99.x-dev" + "dev-master": "1.4-dev" } }, + "autoload": { + "psr-4": { + "Nyholm\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com" + }, + { + "name": "Martijn van der Ven", + "email": "martijn@vanderven.se" + } + ], + "description": "A fast PHP7 implementation of PSR-7", + "homepage": "https://tnyholm.se", + "keywords": [ + "psr-17", + "psr-7" + ], + "support": { + "issues": "https://github.com/Nyholm/psr7/issues", + "source": "https://github.com/Nyholm/psr7/tree/1.4.1" + }, + "funding": [ + { + "url": "https://github.com/Zegnat", + "type": "github" + }, + { + "url": "https://github.com/nyholm", + "type": "github" + } + ], + "time": "2021-07-02T08:32:20+00:00" + }, + { + "name": "ocramius/package-versions", + "version": "2.5.0", + "source": { + "type": "git", + "url": "https://github.com/Ocramius/PackageVersions.git", + "reference": "deded4228eed848fc5eae2fa0149ceb43afd012a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Ocramius/PackageVersions/zipball/deded4228eed848fc5eae2fa0149ceb43afd012a", + "reference": "deded4228eed848fc5eae2fa0149ceb43afd012a", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.2.0", + "php": "~8.0.0 || ~8.1.0" + }, + "replace": { + "composer/package-versions-deprecated": "*" + }, + "require-dev": { + "composer/composer": "^2.2.0", + "doctrine/coding-standard": "^9.0.0", + "ext-zip": "^1.15.0", + "phpunit/phpunit": "^9.5.9", + "roave/infection-static-analysis-plugin": "^1.10.0", + "vimeo/psalm": "^4.10.0" + }, + "type": "library", "autoload": { "psr-4": { "PackageVersions\\": "src/PackageVersions" @@ -3001,10 +4000,10 @@ "email": "ocramius@gmail.com" } ], - "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", + "description": "Provides efficient querying for installed package versions (no runtime IO)", "support": { "issues": "https://github.com/Ocramius/PackageVersions/issues", - "source": "https://github.com/Ocramius/PackageVersions/tree/1.11.x" + "source": "https://github.com/Ocramius/PackageVersions/tree/2.5.0" }, "funding": [ { @@ -3016,7 +4015,7 @@ "type": "tidelift" } ], - "time": "2020-08-21T12:16:47+00:00" + "time": "2021-12-22T12:00:55+00:00" }, { "name": "opis/closure", @@ -3084,69 +4083,407 @@ "time": "2021-04-09T13:42:10+00:00" }, { - "name": "phpcollection/phpcollection", - "version": "0.5.0", + "name": "php-http/client-common", + "version": "2.5.0", "source": { "type": "git", - "url": "https://github.com/schmittjoh/php-collection.git", - "reference": "f2bcff45c0da7c27991bbc1f90f47c4b7fb434a6" + "url": "https://github.com/php-http/client-common.git", + "reference": "d135751167d57e27c74de674d6a30cef2dc8e054" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/php-collection/zipball/f2bcff45c0da7c27991bbc1f90f47c4b7fb434a6", - "reference": "f2bcff45c0da7c27991bbc1f90f47c4b7fb434a6", + "url": "https://api.github.com/repos/php-http/client-common/zipball/d135751167d57e27c74de674d6a30cef2dc8e054", + "reference": "d135751167d57e27c74de674d6a30cef2dc8e054", "shasum": "" }, "require": { - "phpoption/phpoption": "1.*" + "php": "^7.1 || ^8.0", + "php-http/httplug": "^2.0", + "php-http/message": "^1.6", + "php-http/message-factory": "^1.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "symfony/options-resolver": "~4.0.15 || ~4.1.9 || ^4.2.1 || ^5.0 || ^6.0", + "symfony/polyfill-php80": "^1.17" + }, + "require-dev": { + "doctrine/instantiator": "^1.1", + "guzzlehttp/psr7": "^1.4", + "nyholm/psr7": "^1.2", + "phpspec/phpspec": "^5.1 || ^6.3 || ^7.1", + "phpspec/prophecy": "^1.10.2", + "phpunit/phpunit": "^7.5.15 || ^8.5 || ^9.3" + }, + "suggest": { + "ext-json": "To detect JSON responses with the ContentTypePlugin", + "ext-libxml": "To detect XML responses with the ContentTypePlugin", + "php-http/cache-plugin": "PSR-6 Cache plugin", + "php-http/logger-plugin": "PSR-3 Logger plugin", + "php-http/stopwatch-plugin": "Symfony Stopwatch plugin" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "0.4-dev" + "dev-master": "2.3.x-dev" } }, "autoload": { - "psr-0": { - "PhpCollection": "src/" + "psr-4": { + "Http\\Client\\Common\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "Apache2" + "MIT" ], "authors": [ { - "name": "Johannes M. Schmitt", - "email": "schmittjoh@gmail.com" + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" } ], - "description": "General-Purpose Collection Library for PHP", + "description": "Common HTTP Client implementations and tools for HTTPlug", + "homepage": "http://httplug.io", "keywords": [ - "collection", - "list", - "map", - "sequence", - "set" + "client", + "common", + "http", + "httplug" ], "support": { - "issues": "https://github.com/schmittjoh/php-collection/issues", - "source": "https://github.com/schmittjoh/php-collection/tree/master" + "issues": "https://github.com/php-http/client-common/issues", + "source": "https://github.com/php-http/client-common/tree/2.5.0" }, - "time": "2015-05-17T12:39:23+00:00" + "time": "2021-11-26T15:01:24+00:00" }, { - "name": "phpoption/phpoption", - "version": "1.8.0", + "name": "php-http/discovery", + "version": "1.14.1", "source": { "type": "git", - "url": "https://github.com/schmittjoh/php-option.git", - "reference": "5455cb38aed4523f99977c4a12ef19da4bfe2a28" + "url": "https://github.com/php-http/discovery.git", + "reference": "de90ab2b41d7d61609f504e031339776bc8c7223" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/5455cb38aed4523f99977c4a12ef19da4bfe2a28", - "reference": "5455cb38aed4523f99977c4a12ef19da4bfe2a28", + "url": "https://api.github.com/repos/php-http/discovery/zipball/de90ab2b41d7d61609f504e031339776bc8c7223", + "reference": "de90ab2b41d7d61609f504e031339776bc8c7223", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "nyholm/psr7": "<1.0" + }, + "require-dev": { + "graham-campbell/phpspec-skip-example-extension": "^5.0", + "php-http/httplug": "^1.0 || ^2.0", + "php-http/message-factory": "^1.0", + "phpspec/phpspec": "^5.1 || ^6.1", + "puli/composer-plugin": "1.0.0-beta10" + }, + "suggest": { + "php-http/message": "Allow to use Guzzle, Diactoros or Slim Framework factories" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.9-dev" + } + }, + "autoload": { + "psr-4": { + "Http\\Discovery\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "Finds installed HTTPlug implementations and PSR-7 message factories", + "homepage": "http://php-http.org", + "keywords": [ + "adapter", + "client", + "discovery", + "factory", + "http", + "message", + "psr7" + ], + "support": { + "issues": "https://github.com/php-http/discovery/issues", + "source": "https://github.com/php-http/discovery/tree/1.14.1" + }, + "time": "2021-09-18T07:57:46+00:00" + }, + { + "name": "php-http/httplug", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/php-http/httplug.git", + "reference": "191a0a1b41ed026b717421931f8d3bd2514ffbf9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/httplug/zipball/191a0a1b41ed026b717421931f8d3bd2514ffbf9", + "reference": "191a0a1b41ed026b717421931f8d3bd2514ffbf9", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", + "php-http/promise": "^1.1", + "psr/http-client": "^1.0", + "psr/http-message": "^1.0" + }, + "require-dev": { + "friends-of-phpspec/phpspec-code-coverage": "^4.1", + "phpspec/phpspec": "^5.1 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Eric GELOEN", + "email": "geloen.eric@gmail.com" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "HTTPlug, the HTTP client abstraction for PHP", + "homepage": "http://httplug.io", + "keywords": [ + "client", + "http" + ], + "support": { + "issues": "https://github.com/php-http/httplug/issues", + "source": "https://github.com/php-http/httplug/tree/master" + }, + "time": "2020-07-13T15:43:23+00:00" + }, + { + "name": "php-http/message", + "version": "1.12.0", + "source": { + "type": "git", + "url": "https://github.com/php-http/message.git", + "reference": "39eb7548be982a81085fe5a6e2a44268cd586291" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/message/zipball/39eb7548be982a81085fe5a6e2a44268cd586291", + "reference": "39eb7548be982a81085fe5a6e2a44268cd586291", + "shasum": "" + }, + "require": { + "clue/stream-filter": "^1.5", + "php": "^7.1 || ^8.0", + "php-http/message-factory": "^1.0.2", + "psr/http-message": "^1.0" + }, + "provide": { + "php-http/message-factory-implementation": "1.0" + }, + "require-dev": { + "ergebnis/composer-normalize": "^2.6", + "ext-zlib": "*", + "guzzlehttp/psr7": "^1.0", + "laminas/laminas-diactoros": "^2.0", + "phpspec/phpspec": "^5.1 || ^6.3", + "slim/slim": "^3.0" + }, + "suggest": { + "ext-zlib": "Used with compressor/decompressor streams", + "guzzlehttp/psr7": "Used with Guzzle PSR-7 Factories", + "laminas/laminas-diactoros": "Used with Diactoros Factories", + "slim/slim": "Used with Slim Framework PSR-7 implementation" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + }, + "autoload": { + "psr-4": { + "Http\\Message\\": "src/" + }, + "files": [ + "src/filters.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "HTTP Message related tools", + "homepage": "http://php-http.org", + "keywords": [ + "http", + "message", + "psr-7" + ], + "support": { + "issues": "https://github.com/php-http/message/issues", + "source": "https://github.com/php-http/message/tree/1.12.0" + }, + "time": "2021-08-29T09:13:12+00:00" + }, + { + "name": "php-http/message-factory", + "version": "v1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-http/message-factory.git", + "reference": "a478cb11f66a6ac48d8954216cfed9aa06a501a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/message-factory/zipball/a478cb11f66a6ac48d8954216cfed9aa06a501a1", + "reference": "a478cb11f66a6ac48d8954216cfed9aa06a501a1", + "shasum": "" + }, + "require": { + "php": ">=5.4", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "Factory interfaces for PSR-7 HTTP Message", + "homepage": "http://php-http.org", + "keywords": [ + "factory", + "http", + "message", + "stream", + "uri" + ], + "support": { + "issues": "https://github.com/php-http/message-factory/issues", + "source": "https://github.com/php-http/message-factory/tree/master" + }, + "time": "2015-12-19T14:08:53+00:00" + }, + { + "name": "php-http/promise", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-http/promise.git", + "reference": "4c4c1f9b7289a2ec57cde7f1e9762a5789506f88" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/promise/zipball/4c4c1f9b7289a2ec57cde7f1e9762a5789506f88", + "reference": "4c4c1f9b7289a2ec57cde7f1e9762a5789506f88", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "friends-of-phpspec/phpspec-code-coverage": "^4.3.2", + "phpspec/phpspec": "^5.1.2 || ^6.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Http\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Joel Wurtz", + "email": "joel.wurtz@gmail.com" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "Promise used for asynchronous HTTP requests", + "homepage": "http://httplug.io", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/php-http/promise/issues", + "source": "https://github.com/php-http/promise/tree/1.1.0" + }, + "time": "2020-07-07T09:29:14+00:00" + }, + { + "name": "phpoption/phpoption", + "version": "1.8.1", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/php-option.git", + "reference": "eab7a0df01fe2344d172bff4cd6dbd3f8b84ad15" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/eab7a0df01fe2344d172bff4cd6dbd3f8b84ad15", + "reference": "eab7a0df01fe2344d172bff4cd6dbd3f8b84ad15", "shasum": "" }, "require": { @@ -3154,7 +4491,7 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.4.1", - "phpunit/phpunit": "^6.5.14 || ^7.0.20 || ^8.5.19 || ^9.5.8" + "phpunit/phpunit": "^6.5.14 || ^7.5.20 || ^8.5.19 || ^9.5.8" }, "type": "library", "extra": { @@ -3174,11 +4511,13 @@ "authors": [ { "name": "Johannes M. Schmitt", - "email": "schmittjoh@gmail.com" + "email": "schmittjoh@gmail.com", + "homepage": "https://github.com/schmittjoh" }, { "name": "Graham Campbell", - "email": "hello@gjcampbell.co.uk" + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" } ], "description": "Option Type for PHP", @@ -3190,7 +4529,7 @@ ], "support": { "issues": "https://github.com/schmittjoh/php-option/issues", - "source": "https://github.com/schmittjoh/php-option/tree/1.8.0" + "source": "https://github.com/schmittjoh/php-option/tree/1.8.1" }, "funding": [ { @@ -3202,20 +4541,69 @@ "type": "tidelift" } ], - "time": "2021-08-28T21:27:29+00:00" + "time": "2021-12-04T23:24:31+00:00" }, { - "name": "predis/predis", - "version": "v1.1.7", + "name": "phpstan/phpdoc-parser", + "version": "1.2.0", "source": { "type": "git", - "url": "https://github.com/predis/predis.git", - "reference": "b240daa106d4e02f0c5b7079b41e31ddf66fddf8" + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "dbc093d7af60eff5cd575d2ed761b15ed40bd08e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/predis/predis/zipball/b240daa106d4e02f0c5b7079b41e31ddf66fddf8", - "reference": "b240daa106d4e02f0c5b7079b41e31ddf66fddf8", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/dbc093d7af60eff5cd575d2ed761b15ed40bd08e", + "reference": "dbc093d7af60eff5cd575d2ed761b15ed40bd08e", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.5", + "symfony/process": "^5.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.2.0" + }, + "time": "2021-09-16T20:46:02+00:00" + }, + { + "name": "predis/predis", + "version": "v1.1.9", + "source": { + "type": "git", + "url": "https://github.com/predis/predis.git", + "reference": "c50c3393bb9f47fa012d0cdfb727a266b0818259" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/predis/predis/zipball/c50c3393bb9f47fa012d0cdfb727a266b0818259", + "reference": "c50c3393bb9f47fa012d0cdfb727a266b0818259", "shasum": "" }, "require": { @@ -3260,7 +4648,7 @@ ], "support": { "issues": "https://github.com/predis/predis/issues", - "source": "https://github.com/predis/predis/tree/v1.1.7" + "source": "https://github.com/predis/predis/tree/v1.1.9" }, "funding": [ { @@ -3268,24 +4656,24 @@ "type": "github" } ], - "time": "2021-04-04T19:34:46+00:00" + "time": "2021-10-05T19:02:38+00:00" }, { "name": "psr/cache", - "version": "1.0.1", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/php-fig/cache.git", - "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", - "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=8.0.0" }, "type": "library", "extra": { @@ -3305,7 +4693,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interface for caching libraries", @@ -3315,26 +4703,26 @@ "psr-6" ], "support": { - "source": "https://github.com/php-fig/cache/tree/master" + "source": "https://github.com/php-fig/cache/tree/3.0.0" }, - "time": "2016-08-06T20:24:11+00:00" + "time": "2021-02-03T23:26:27+00:00" }, { "name": "psr/container", - "version": "1.1.1", + "version": "1.1.2", "source": { "type": "git", "url": "https://github.com/php-fig/container.git", - "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf" + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/8622567409010282b7aeebe4bb841fe98b58dcaf", - "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", "shasum": "" }, "require": { - "php": ">=7.2.0" + "php": ">=7.4.0" }, "type": "library", "autoload": { @@ -3363,9 +4751,166 @@ ], "support": { "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/1.1.1" + "source": "https://github.com/php-fig/container/tree/1.1.2" }, - "time": "2021-03-05T17:36:06+00:00" + "time": "2021-11-05T16:50:12+00:00" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "psr/http-client", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client/tree/master" + }, + "time": "2020-06-29T06:28:15+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" }, { "name": "psr/http-message", @@ -3422,30 +4967,30 @@ }, { "name": "psr/log", - "version": "1.1.4", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + "reference": "ef29f6d262798707a9edd554e2b82517ef3a9376" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "url": "https://api.github.com/repos/php-fig/log/zipball/ef29f6d262798707a9edd554e2b82517ef3a9376", + "reference": "ef29f6d262798707a9edd554e2b82517ef3a9376", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=8.0.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { "psr-4": { - "Psr\\Log\\": "Psr/Log/" + "Psr\\Log\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -3466,9 +5011,9 @@ "psr-3" ], "support": { - "source": "https://github.com/php-fig/log/tree/1.1.4" + "source": "https://github.com/php-fig/log/tree/2.0.0" }, - "time": "2021-05-03T11:20:27+00:00" + "time": "2021-07-14T16:41:46+00:00" }, { "name": "psr/simple-cache", @@ -3523,32 +5068,29 @@ }, { "name": "psy/psysh", - "version": "v0.9.12", + "version": "v0.10.12", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "90da7f37568aee36b116a030c5f99c915267edd4" + "reference": "a0d9981aa07ecfcbea28e4bfa868031cca121e7d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/90da7f37568aee36b116a030c5f99c915267edd4", - "reference": "90da7f37568aee36b116a030c5f99c915267edd4", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/a0d9981aa07ecfcbea28e4bfa868031cca121e7d", + "reference": "a0d9981aa07ecfcbea28e4bfa868031cca121e7d", "shasum": "" }, "require": { - "dnoegel/php-xdg-base-dir": "0.1.*", "ext-json": "*", "ext-tokenizer": "*", - "jakub-onderka/php-console-highlighter": "0.3.*|0.4.*", - "nikic/php-parser": "~1.3|~2.0|~3.0|~4.0", - "php": ">=5.4.0", - "symfony/console": "~2.3.10|^2.4.2|~3.0|~4.0|~5.0", - "symfony/var-dumper": "~2.7|~3.0|~4.0|~5.0" + "nikic/php-parser": "~4.0|~3.0|~2.0|~1.3", + "php": "^8.0 || ^7.0 || ^5.5.9", + "symfony/console": "~5.0|~4.0|~3.0|^2.4.2|~2.3.10", + "symfony/var-dumper": "~5.0|~4.0|~3.0|~2.7" }, "require-dev": { "bamarni/composer-bin-plugin": "^1.2", - "hoa/console": "~2.15|~3.16", - "phpunit/phpunit": "~4.8.35|~5.0|~6.0|~7.0" + "hoa/console": "3.17.*" }, "suggest": { "ext-pcntl": "Enabling the PCNTL extension makes PsySH a lot happier :)", @@ -3563,7 +5105,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-develop": "0.9.x-dev" + "dev-main": "0.10.x-dev" } }, "autoload": { @@ -3595,9 +5137,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.9.12" + "source": "https://github.com/bobthecow/psysh/tree/v0.10.12" }, - "time": "2019-12-06T14:19:43+00:00" + "time": "2021-11-30T14:05:36+00:00" }, { "name": "ralouphie/getallheaders", @@ -3644,29 +5186,518 @@ "time": "2019-03-08T08:55:37+00:00" }, { - "name": "symfony/browser-kit", - "version": "v4.4.27", + "name": "ramsey/collection", + "version": "1.2.2", "source": { "type": "git", - "url": "https://github.com/symfony/browser-kit.git", - "reference": "9629d1524d8ced5a4ec3e94abdbd638b4ec8319b" + "url": "https://github.com/ramsey/collection.git", + "reference": "cccc74ee5e328031b15640b51056ee8d3bb66c0a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/browser-kit/zipball/9629d1524d8ced5a4ec3e94abdbd638b4ec8319b", - "reference": "9629d1524d8ced5a4ec3e94abdbd638b4ec8319b", + "url": "https://api.github.com/repos/ramsey/collection/zipball/cccc74ee5e328031b15640b51056ee8d3bb66c0a", + "reference": "cccc74ee5e328031b15640b51056ee8d3bb66c0a", "shasum": "" }, "require": { - "php": ">=7.1.3", - "symfony/dom-crawler": "^3.4|^4.0|^5.0", + "php": "^7.3 || ^8", + "symfony/polyfill-php81": "^1.23" + }, + "require-dev": { + "captainhook/captainhook": "^5.3", + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", + "ergebnis/composer-normalize": "^2.6", + "fakerphp/faker": "^1.5", + "hamcrest/hamcrest-php": "^2", + "jangregor/phpstan-prophecy": "^0.8", + "mockery/mockery": "^1.3", + "phpspec/prophecy-phpunit": "^2.0", + "phpstan/extension-installer": "^1", + "phpstan/phpstan": "^0.12.32", + "phpstan/phpstan-mockery": "^0.12.5", + "phpstan/phpstan-phpunit": "^0.12.11", + "phpunit/phpunit": "^8.5 || ^9", + "psy/psysh": "^0.10.4", + "slevomat/coding-standard": "^6.3", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^4.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Ramsey\\Collection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" + } + ], + "description": "A PHP library for representing and manipulating collections.", + "keywords": [ + "array", + "collection", + "hash", + "map", + "queue", + "set" + ], + "support": { + "issues": "https://github.com/ramsey/collection/issues", + "source": "https://github.com/ramsey/collection/tree/1.2.2" + }, + "funding": [ + { + "url": "https://github.com/ramsey", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/ramsey/collection", + "type": "tidelift" + } + ], + "time": "2021-10-10T03:01:02+00:00" + }, + { + "name": "ramsey/uuid", + "version": "4.2.3", + "source": { + "type": "git", + "url": "https://github.com/ramsey/uuid.git", + "reference": "fc9bb7fb5388691fd7373cd44dcb4d63bbcf24df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/fc9bb7fb5388691fd7373cd44dcb4d63bbcf24df", + "reference": "fc9bb7fb5388691fd7373cd44dcb4d63bbcf24df", + "shasum": "" + }, + "require": { + "brick/math": "^0.8 || ^0.9", + "ext-json": "*", + "php": "^7.2 || ^8.0", + "ramsey/collection": "^1.0", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-php80": "^1.14" + }, + "replace": { + "rhumsaa/uuid": "self.version" + }, + "require-dev": { + "captainhook/captainhook": "^5.10", + "captainhook/plugin-composer": "^5.3", + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", + "doctrine/annotations": "^1.8", + "ergebnis/composer-normalize": "^2.15", + "mockery/mockery": "^1.3", + "moontoast/math": "^1.1", + "paragonie/random-lib": "^2", + "php-mock/php-mock": "^2.2", + "php-mock/php-mock-mockery": "^1.3", + "php-parallel-lint/php-parallel-lint": "^1.1", + "phpbench/phpbench": "^1.0", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-mockery": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpunit/phpunit": "^8.5 || ^9", + "slevomat/coding-standard": "^7.0", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^4.9" + }, + "suggest": { + "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", + "ext-ctype": "Enables faster processing of character classification using ctype functions.", + "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.", + "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.", + "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", + "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.x-dev" + }, + "captainhook": { + "force-install": true + } + }, + "autoload": { + "psr-4": { + "Ramsey\\Uuid\\": "src/" + }, + "files": [ + "src/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", + "keywords": [ + "guid", + "identifier", + "uuid" + ], + "support": { + "issues": "https://github.com/ramsey/uuid/issues", + "source": "https://github.com/ramsey/uuid/tree/4.2.3" + }, + "funding": [ + { + "url": "https://github.com/ramsey", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/ramsey/uuid", + "type": "tidelift" + } + ], + "time": "2021-09-25T23:10:38+00:00" + }, + { + "name": "sentry/sdk", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/getsentry/sentry-php-sdk.git", + "reference": "2de7de3233293f80d1e244bd950adb2121a3731c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/getsentry/sentry-php-sdk/zipball/2de7de3233293f80d1e244bd950adb2121a3731c", + "reference": "2de7de3233293f80d1e244bd950adb2121a3731c", + "shasum": "" + }, + "require": { + "http-interop/http-factory-guzzle": "^1.0", + "sentry/sentry": "^3.1", + "symfony/http-client": "^4.3|^5.0|^6.0" + }, + "type": "metapackage", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Sentry", + "email": "accounts@sentry.io" + } + ], + "description": "This is a metapackage shipping sentry/sentry with a recommended HTTP client.", + "homepage": "http://sentry.io", + "keywords": [ + "crash-reporting", + "crash-reports", + "error-handler", + "error-monitoring", + "log", + "logging", + "sentry" + ], + "support": { + "source": "https://github.com/getsentry/sentry-php-sdk/tree/3.1.1" + }, + "funding": [ + { + "url": "https://sentry.io/", + "type": "custom" + }, + { + "url": "https://sentry.io/pricing/", + "type": "custom" + } + ], + "time": "2021-11-30T11:54:41+00:00" + }, + { + "name": "sentry/sentry", + "version": "3.3.4", + "source": { + "type": "git", + "url": "https://github.com/getsentry/sentry-php.git", + "reference": "ecbd09ea5d053a202cf773cb24ab28af820831bd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/getsentry/sentry-php/zipball/ecbd09ea5d053a202cf773cb24ab28af820831bd", + "reference": "ecbd09ea5d053a202cf773cb24ab28af820831bd", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-mbstring": "*", + "guzzlehttp/promises": "^1.4", + "guzzlehttp/psr7": "^1.7|^2.0", + "jean85/pretty-package-versions": "^1.5|^2.0.1", + "php": "^7.2|^8.0", + "php-http/async-client-implementation": "^1.0", + "php-http/client-common": "^1.5|^2.0", + "php-http/discovery": "^1.6.1", + "php-http/httplug": "^1.1|^2.0", + "php-http/message": "^1.5", + "psr/http-factory": "^1.0", + "psr/http-message-implementation": "^1.0", + "psr/log": "^1.0|^2.0|^3.0", + "symfony/options-resolver": "^3.4.43|^4.4.30|^5.0.11|^6.0", + "symfony/polyfill-php80": "^1.17", + "symfony/polyfill-uuid": "^1.13.1" + }, + "conflict": { + "php-http/client-common": "1.8.0", + "raven/raven": "*" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.17", + "http-interop/http-factory-guzzle": "^1.0", + "monolog/monolog": "^1.3|^2.0", + "nikic/php-parser": "^4.10.3", + "php-http/mock-client": "^1.3", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpunit/phpunit": "^8.5.14|^9.4", + "symfony/phpunit-bridge": "^5.2|^6.0", + "vimeo/psalm": "^4.2" + }, + "suggest": { + "monolog/monolog": "Allow sending log messages to Sentry by using the included Monolog handler." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3.x-dev" + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Sentry\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sentry", + "email": "accounts@sentry.io" + } + ], + "description": "A PHP SDK for Sentry (http://sentry.io)", + "homepage": "http://sentry.io", + "keywords": [ + "crash-reporting", + "crash-reports", + "error-handler", + "error-monitoring", + "log", + "logging", + "sentry" + ], + "support": { + "issues": "https://github.com/getsentry/sentry-php/issues", + "source": "https://github.com/getsentry/sentry-php/tree/3.3.4" + }, + "funding": [ + { + "url": "https://sentry.io/", + "type": "custom" + }, + { + "url": "https://sentry.io/pricing/", + "type": "custom" + } + ], + "time": "2021-11-08T08:44:00+00:00" + }, + { + "name": "sentry/sentry-laravel", + "version": "2.10.2", + "source": { + "type": "git", + "url": "https://github.com/getsentry/sentry-laravel.git", + "reference": "82cf98c830d73f80dc67cf21381da4ede4c3989e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/getsentry/sentry-laravel/zipball/82cf98c830d73f80dc67cf21381da4ede4c3989e", + "reference": "82cf98c830d73f80dc67cf21381da4ede4c3989e", + "shasum": "" + }, + "require": { + "illuminate/support": "5.0 - 5.8 | ^6.0 | ^7.0 | ^8.0", + "nyholm/psr7": "^1.0", + "php": "^7.2 | ^8.0", + "sentry/sdk": "^3.1", + "sentry/sentry": "^3.3", + "symfony/psr-http-message-bridge": "^1.0 | ^2.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "2.18.*", + "laravel/framework": "5.0 - 5.8 | ^6.0 | ^7.0 | ^8.0", + "mockery/mockery": "1.3.*", + "orchestra/testbench": "3.1 - 3.8 | ^4.7 | ^5.1 | ^6.0", + "phpunit/phpunit": "^5.7 | ^6.5 | ^7.5 | ^8.4 | ^9.3" + }, + "suggest": { + "zendframework/zend-diactoros": "When using Laravel >=5.1 - <=6.9 this package can help get more accurate request info, not used on Laravel >=6.10 anymore (https://laravel.com/docs/5.8/requests#psr7-requests)" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev", + "dev-0.x": "0.x-dev" + }, + "laravel": { + "providers": [ + "Sentry\\Laravel\\ServiceProvider", + "Sentry\\Laravel\\Tracing\\ServiceProvider" + ], + "aliases": { + "Sentry": "Sentry\\Laravel\\Facade" + } + } + }, + "autoload": { + "psr-0": { + "Sentry\\Laravel\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Sentry", + "email": "accounts@sentry.io" + } + ], + "description": "Laravel SDK for Sentry (https://sentry.io)", + "homepage": "https://sentry.io", + "keywords": [ + "crash-reporting", + "crash-reports", + "error-handler", + "error-monitoring", + "laravel", + "log", + "logging", + "sentry" + ], + "support": { + "issues": "https://github.com/getsentry/sentry-laravel/issues", + "source": "https://github.com/getsentry/sentry-laravel/tree/2.10.2" + }, + "funding": [ + { + "url": "https://sentry.io/", + "type": "custom" + }, + { + "url": "https://sentry.io/pricing/", + "type": "custom" + } + ], + "time": "2021-11-16T19:20:42+00:00" + }, + { + "name": "swagger-api/swagger-ui", + "version": "v3.52.5", + "source": { + "type": "git", + "url": "https://github.com/swagger-api/swagger-ui.git", + "reference": "f1ad60dc92e7edb0898583e16c3e66fe3e9eada2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/swagger-api/swagger-ui/zipball/f1ad60dc92e7edb0898583e16c3e66fe3e9eada2", + "reference": "f1ad60dc92e7edb0898583e16c3e66fe3e9eada2", + "shasum": "" + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Anna Bodnia", + "email": "anna.bodnia@gmail.com" + }, + { + "name": "Buu Nguyen", + "email": "buunguyen@gmail.com" + }, + { + "name": "Josh Ponelat", + "email": "jponelat@gmail.com" + }, + { + "name": "Kyle Shockey", + "email": "kyleshockey1@gmail.com" + }, + { + "name": "Robert Barnwell", + "email": "robert@robertismy.name" + }, + { + "name": "Sahar Jafari", + "email": "shr.jafari@gmail.com" + } + ], + "description": " Swagger UI is a collection of HTML, Javascript, and CSS assets that dynamically generate beautiful documentation from a Swagger-compliant API.", + "homepage": "http://swagger.io", + "keywords": [ + "api", + "documentation", + "openapi", + "specification", + "swagger", + "ui" + ], + "support": { + "issues": "https://github.com/swagger-api/swagger-ui/issues", + "source": "https://github.com/swagger-api/swagger-ui/tree/v3.52.5" + }, + "time": "2021-10-14T14:25:14+00:00" + }, + { + "name": "symfony/browser-kit", + "version": "v5.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/browser-kit.git", + "reference": "d250db364a35ba5d60626b2a6f10f2eaf2073bde" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/d250db364a35ba5d60626b2a6f10f2eaf2073bde", + "reference": "d250db364a35ba5d60626b2a6f10f2eaf2073bde", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/dom-crawler": "^4.4|^5.0|^6.0", "symfony/polyfill-php80": "^1.16" }, "require-dev": { - "symfony/css-selector": "^3.4|^4.0|^5.0", - "symfony/http-client": "^4.3|^5.0", - "symfony/mime": "^4.3|^5.0", - "symfony/process": "^3.4|^4.0|^5.0" + "symfony/css-selector": "^4.4|^5.0|^6.0", + "symfony/http-client": "^4.4|^5.0|^6.0", + "symfony/mime": "^4.4|^5.0|^6.0", + "symfony/process": "^4.4|^5.0|^6.0" }, "suggest": { "symfony/process": "" @@ -3697,7 +5728,7 @@ "description": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/browser-kit/tree/v4.4.27" + "source": "https://github.com/symfony/browser-kit/tree/v5.4.0" }, "funding": [ { @@ -3713,47 +5744,50 @@ "type": "tidelift" } ], - "time": "2021-07-21T12:19:41+00:00" + "time": "2021-10-26T22:29:18+00:00" }, { "name": "symfony/console", - "version": "v4.4.30", + "version": "v5.4.1", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "a3f7189a0665ee33b50e9e228c46f50f5acbed22" + "reference": "9130e1a0fc93cb0faadca4ee917171bd2ca9e5f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/a3f7189a0665ee33b50e9e228c46f50f5acbed22", - "reference": "a3f7189a0665ee33b50e9e228c46f50f5acbed22", + "url": "https://api.github.com/repos/symfony/console/zipball/9130e1a0fc93cb0faadca4ee917171bd2ca9e5f4", + "reference": "9130e1a0fc93cb0faadca4ee917171bd2ca9e5f4", "shasum": "" }, "require": { - "php": ">=7.1.3", + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php73": "^1.8", + "symfony/polyfill-php73": "^1.9", "symfony/polyfill-php80": "^1.16", - "symfony/service-contracts": "^1.1|^2" + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/string": "^5.1|^6.0" }, "conflict": { "psr/log": ">=3", - "symfony/dependency-injection": "<3.4", - "symfony/event-dispatcher": "<4.3|>=5", + "symfony/dependency-injection": "<4.4", + "symfony/dotenv": "<5.1", + "symfony/event-dispatcher": "<4.4", "symfony/lock": "<4.4", - "symfony/process": "<3.3" + "symfony/process": "<4.4" }, "provide": { "psr/log-implementation": "1.0|2.0" }, "require-dev": { "psr/log": "^1|^2", - "symfony/config": "^3.4|^4.0|^5.0", - "symfony/dependency-injection": "^3.4|^4.0|^5.0", - "symfony/event-dispatcher": "^4.3", - "symfony/lock": "^4.4|^5.0", - "symfony/process": "^3.4|^4.0|^5.0", - "symfony/var-dumper": "^4.3|^5.0" + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/event-dispatcher": "^4.4|^5.0|^6.0", + "symfony/lock": "^4.4|^5.0|^6.0", + "symfony/process": "^4.4|^5.0|^6.0", + "symfony/var-dumper": "^4.4|^5.0|^6.0" }, "suggest": { "psr/log": "For using the console logger", @@ -3786,8 +5820,14 @@ ], "description": "Eases the creation of beautiful and testable command line interfaces", "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command line", + "console", + "terminal" + ], "support": { - "source": "https://github.com/symfony/console/tree/v4.4.30" + "source": "https://github.com/symfony/console/tree/v5.4.1" }, "funding": [ { @@ -3803,24 +5843,24 @@ "type": "tidelift" } ], - "time": "2021-08-25T19:27:26+00:00" + "time": "2021-12-09T11:22:43+00:00" }, { "name": "symfony/css-selector", - "version": "v4.4.27", + "version": "v5.4.0", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "5194f18bd80d106f11efa8f7cd0fbdcc3af96ce6" + "reference": "44b933f98bb4b5220d10bed9ce5662f8c2d13dcc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/5194f18bd80d106f11efa8f7cd0fbdcc3af96ce6", - "reference": "5194f18bd80d106f11efa8f7cd0fbdcc3af96ce6", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/44b933f98bb4b5220d10bed9ce5662f8c2d13dcc", + "reference": "44b933f98bb4b5220d10bed9ce5662f8c2d13dcc", "shasum": "" }, "require": { - "php": ">=7.1.3", + "php": ">=7.2.5", "symfony/polyfill-php80": "^1.16" }, "type": "library", @@ -3853,7 +5893,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v4.4.27" + "source": "https://github.com/symfony/css-selector/tree/v5.4.0" }, "funding": [ { @@ -3869,97 +5909,29 @@ "type": "tidelift" } ], - "time": "2021-07-21T12:19:41+00:00" - }, - { - "name": "symfony/debug", - "version": "v4.4.27", - "source": { - "type": "git", - "url": "https://github.com/symfony/debug.git", - "reference": "2f9160e92eb64c95da7368c867b663a8e34e980c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/2f9160e92eb64c95da7368c867b663a8e34e980c", - "reference": "2f9160e92eb64c95da7368c867b663a8e34e980c", - "shasum": "" - }, - "require": { - "php": ">=7.1.3", - "psr/log": "^1|^2|^3" - }, - "conflict": { - "symfony/http-kernel": "<3.4" - }, - "require-dev": { - "symfony/http-kernel": "^3.4|^4.0|^5.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Debug\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides tools to ease debugging PHP code", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/debug/tree/v4.4.27" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-07-22T07:21:39+00:00" + "time": "2021-09-09T08:06:01+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v2.4.0", + "version": "v3.0.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627" + "reference": "c726b64c1ccfe2896cb7df2e1331c357ad1c8ced" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/5f38c8804a9e97d23e0c8d63341088cd8a22d627", - "reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/c726b64c1ccfe2896cb7df2e1331c357ad1c8ced", + "reference": "c726b64c1ccfe2896cb7df2e1331c357ad1c8ced", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=8.0.2" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "2.4-dev" + "dev-main": "3.0-dev" }, "thanks": { "name": "symfony/contracts", @@ -3988,7 +5960,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v2.4.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.0.0" }, "funding": [ { @@ -4004,24 +5976,25 @@ "type": "tidelift" } ], - "time": "2021-03-23T23:28:01+00:00" + "time": "2021-11-01T23:48:49+00:00" }, { "name": "symfony/dom-crawler", - "version": "v4.4.30", + "version": "v5.4.0", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "4632ae3567746c7e915c33c67a2fb6ab746090c4" + "reference": "5b06626e940a3ad54e573511d64d4e00dc8d0fd8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/4632ae3567746c7e915c33c67a2fb6ab746090c4", - "reference": "4632ae3567746c7e915c33c67a2fb6ab746090c4", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/5b06626e940a3ad54e573511d64d4e00dc8d0fd8", + "reference": "5b06626e940a3ad54e573511d64d4e00dc8d0fd8", "shasum": "" }, "require": { - "php": ">=7.1.3", + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.0", "symfony/polyfill-php80": "^1.16" @@ -4031,7 +6004,7 @@ }, "require-dev": { "masterminds/html5": "^2.6", - "symfony/css-selector": "^3.4|^4.0|^5.0" + "symfony/css-selector": "^4.4|^5.0|^6.0" }, "suggest": { "symfony/css-selector": "" @@ -4062,7 +6035,7 @@ "description": "Eases DOM navigation for HTML and XML documents", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dom-crawler/tree/v4.4.30" + "source": "https://github.com/symfony/dom-crawler/tree/v5.4.0" }, "funding": [ { @@ -4078,32 +6051,35 @@ "type": "tidelift" } ], - "time": "2021-08-28T15:40:01+00:00" + "time": "2021-11-23T10:19:22+00:00" }, { "name": "symfony/error-handler", - "version": "v4.4.30", + "version": "v5.4.1", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "51f98f7aa99f00f3b1da6bafe934e67ae6ba6dc5" + "reference": "1e3cb3565af49cd5f93e5787500134500a29f0d9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/51f98f7aa99f00f3b1da6bafe934e67ae6ba6dc5", - "reference": "51f98f7aa99f00f3b1da6bafe934e67ae6ba6dc5", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/1e3cb3565af49cd5f93e5787500134500a29f0d9", + "reference": "1e3cb3565af49cd5f93e5787500134500a29f0d9", "shasum": "" }, "require": { - "php": ">=7.1.3", + "php": ">=7.2.5", "psr/log": "^1|^2|^3", - "symfony/debug": "^4.4.5", - "symfony/var-dumper": "^4.4|^5.0" + "symfony/var-dumper": "^4.4|^5.0|^6.0" }, "require-dev": { - "symfony/http-kernel": "^4.4|^5.0", - "symfony/serializer": "^4.4|^5.0" + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/http-kernel": "^4.4|^5.0|^6.0", + "symfony/serializer": "^4.4|^5.0|^6.0" }, + "bin": [ + "Resources/bin/patch-type-declarations" + ], "type": "library", "autoload": { "psr-4": { @@ -4130,7 +6106,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v4.4.30" + "source": "https://github.com/symfony/error-handler/tree/v5.4.1" }, "funding": [ { @@ -4146,43 +6122,42 @@ "type": "tidelift" } ], - "time": "2021-08-27T17:42:48+00:00" + "time": "2021-12-01T15:04:08+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v4.4.30", + "version": "v6.0.1", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "2fe81680070043c4c80e7cedceb797e34f377bac" + "reference": "4f06d19a5f78087061f9de6df3269c139c3d289d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/2fe81680070043c4c80e7cedceb797e34f377bac", - "reference": "2fe81680070043c4c80e7cedceb797e34f377bac", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/4f06d19a5f78087061f9de6df3269c139c3d289d", + "reference": "4f06d19a5f78087061f9de6df3269c139c3d289d", "shasum": "" }, "require": { - "php": ">=7.1.3", - "symfony/event-dispatcher-contracts": "^1.1", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.0.2", + "symfony/event-dispatcher-contracts": "^2|^3" }, "conflict": { - "symfony/dependency-injection": "<3.4" + "symfony/dependency-injection": "<5.4" }, "provide": { "psr/event-dispatcher-implementation": "1.0", - "symfony/event-dispatcher-implementation": "1.1" + "symfony/event-dispatcher-implementation": "2.0|3.0" }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/config": "^3.4|^4.0|^5.0", - "symfony/dependency-injection": "^3.4|^4.0|^5.0", - "symfony/error-handler": "~3.4|~4.4", - "symfony/expression-language": "^3.4|^4.0|^5.0", - "symfony/http-foundation": "^3.4|^4.0|^5.0", - "symfony/service-contracts": "^1.1|^2", - "symfony/stopwatch": "^3.4|^4.0|^5.0" + "symfony/config": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/error-handler": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/http-foundation": "^5.4|^6.0", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/stopwatch": "^5.4|^6.0" }, "suggest": { "symfony/dependency-injection": "", @@ -4214,7 +6189,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v4.4.30" + "source": "https://github.com/symfony/event-dispatcher/tree/v6.0.1" }, "funding": [ { @@ -4230,33 +6205,33 @@ "type": "tidelift" } ], - "time": "2021-08-04T20:31:23+00:00" + "time": "2021-12-08T15:13:44+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v1.1.9", + "version": "v3.0.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "84e23fdcd2517bf37aecbd16967e83f0caee25a7" + "reference": "aa5422287b75594b90ee9cd807caf8f0df491385" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/84e23fdcd2517bf37aecbd16967e83f0caee25a7", - "reference": "84e23fdcd2517bf37aecbd16967e83f0caee25a7", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/aa5422287b75594b90ee9cd807caf8f0df491385", + "reference": "aa5422287b75594b90ee9cd807caf8f0df491385", "shasum": "" }, "require": { - "php": ">=7.1.3" + "php": ">=8.0.2", + "psr/event-dispatcher": "^1" }, "suggest": { - "psr/event-dispatcher": "", "symfony/event-dispatcher-implementation": "" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1-dev" + "dev-main": "3.0-dev" }, "thanks": { "name": "symfony/contracts", @@ -4293,7 +6268,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v1.1.9" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.0.0" }, "funding": [ { @@ -4309,24 +6284,25 @@ "type": "tidelift" } ], - "time": "2020-07-06T13:19:58+00:00" + "time": "2021-07-15T12:33:35+00:00" }, { "name": "symfony/finder", - "version": "v4.4.30", + "version": "v5.4.0", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "70362f1e112280d75b30087c7598b837c1b468b6" + "reference": "d2f29dac98e96a98be467627bd49c2efb1bc2590" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/70362f1e112280d75b30087c7598b837c1b468b6", - "reference": "70362f1e112280d75b30087c7598b837c1b468b6", + "url": "https://api.github.com/repos/symfony/finder/zipball/d2f29dac98e96a98be467627bd49c2efb1bc2590", + "reference": "d2f29dac98e96a98be467627bd49c2efb1bc2590", "shasum": "" }, "require": { - "php": ">=7.1.3", + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", "symfony/polyfill-php80": "^1.16" }, "type": "library", @@ -4355,7 +6331,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v4.4.30" + "source": "https://github.com/symfony/finder/tree/v5.4.0" }, "funding": [ { @@ -4371,20 +6347,107 @@ "type": "tidelift" } ], - "time": "2021-08-04T20:31:23+00:00" + "time": "2021-11-28T15:25:38+00:00" }, { - "name": "symfony/http-client-contracts", - "version": "v2.4.0", + "name": "symfony/http-client", + "version": "v5.4.1", "source": { "type": "git", - "url": "https://github.com/symfony/http-client-contracts.git", - "reference": "7e82f6084d7cae521a75ef2cb5c9457bbda785f4" + "url": "https://github.com/symfony/http-client.git", + "reference": "78b69fc4532253f3025db7f2429d8765e506cbf2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/7e82f6084d7cae521a75ef2cb5c9457bbda785f4", - "reference": "7e82f6084d7cae521a75ef2cb5c9457bbda785f4", + "url": "https://api.github.com/repos/symfony/http-client/zipball/78b69fc4532253f3025db7f2429d8765e506cbf2", + "reference": "78b69fc4532253f3025db7f2429d8765e506cbf2", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/http-client-contracts": "^2.4", + "symfony/polyfill-php73": "^1.11", + "symfony/polyfill-php80": "^1.16", + "symfony/service-contracts": "^1.0|^2|^3" + }, + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "1.0", + "symfony/http-client-implementation": "2.4" + }, + "require-dev": { + "amphp/amp": "^2.5", + "amphp/http-client": "^4.2.1", + "amphp/http-tunnel": "^1.0", + "amphp/socket": "^1.1", + "guzzlehttp/promises": "^1.4", + "nyholm/psr7": "^1.0", + "php-http/httplug": "^1.0|^2.0", + "psr/http-client": "^1.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/http-kernel": "^4.4.13|^5.1.5|^6.0", + "symfony/process": "^4.4|^5.0|^6.0", + "symfony/stopwatch": "^4.4|^5.0|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-client/tree/v5.4.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-12-01T15:04:08+00:00" + }, + { + "name": "symfony/http-client-contracts", + "version": "v2.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client-contracts.git", + "reference": "ec82e57b5b714dbb69300d348bd840b345e24166" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/ec82e57b5b714dbb69300d348bd840b345e24166", + "reference": "ec82e57b5b714dbb69300d348bd840b345e24166", "shasum": "" }, "require": { @@ -4396,7 +6459,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "2.4-dev" + "dev-main": "2.5-dev" }, "thanks": { "name": "symfony/contracts", @@ -4433,7 +6496,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/http-client-contracts/tree/v2.4.0" + "source": "https://github.com/symfony/http-client-contracts/tree/v2.5.0" }, "funding": [ { @@ -4449,31 +6512,36 @@ "type": "tidelift" } ], - "time": "2021-04-11T23:07:08+00:00" + "time": "2021-11-03T09:24:47+00:00" }, { "name": "symfony/http-foundation", - "version": "v4.4.30", + "version": "v5.4.1", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "09b3202651ab23ac8dcf455284a48a3500e56731" + "reference": "5dad3780023a707f4c24beac7d57aead85c1ce3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/09b3202651ab23ac8dcf455284a48a3500e56731", - "reference": "09b3202651ab23ac8dcf455284a48a3500e56731", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/5dad3780023a707f4c24beac7d57aead85c1ce3c", + "reference": "5dad3780023a707f4c24beac7d57aead85c1ce3c", "shasum": "" }, "require": { - "php": ">=7.1.3", - "symfony/mime": "^4.3|^5.0", + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", "symfony/polyfill-mbstring": "~1.1", "symfony/polyfill-php80": "^1.16" }, "require-dev": { "predis/predis": "~1.0", - "symfony/expression-language": "^3.4|^4.0|^5.0" + "symfony/cache": "^4.4|^5.0|^6.0", + "symfony/expression-language": "^4.4|^5.0|^6.0", + "symfony/mime": "^4.4|^5.0|^6.0" + }, + "suggest": { + "symfony/mime": "To use the file extension guesser" }, "type": "library", "autoload": { @@ -4501,7 +6569,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v4.4.30" + "source": "https://github.com/symfony/http-foundation/tree/v5.4.1" }, "funding": [ { @@ -4517,61 +6585,69 @@ "type": "tidelift" } ], - "time": "2021-08-26T15:51:23+00:00" + "time": "2021-12-09T12:46:57+00:00" }, { "name": "symfony/http-kernel", - "version": "v4.4.30", + "version": "v5.4.1", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "87f7ea4a8a7a30c967e26001de99f12943bf57ae" + "reference": "2bdace75c9d6a6eec7e318801b7dc87a72375052" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/87f7ea4a8a7a30c967e26001de99f12943bf57ae", - "reference": "87f7ea4a8a7a30c967e26001de99f12943bf57ae", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/2bdace75c9d6a6eec7e318801b7dc87a72375052", + "reference": "2bdace75c9d6a6eec7e318801b7dc87a72375052", "shasum": "" }, "require": { - "php": ">=7.1.3", + "php": ">=7.2.5", "psr/log": "^1|^2", - "symfony/error-handler": "^4.4", - "symfony/event-dispatcher": "^4.4", - "symfony/http-client-contracts": "^1.1|^2", - "symfony/http-foundation": "^4.4.30|^5.3.7", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/error-handler": "^4.4|^5.0|^6.0", + "symfony/event-dispatcher": "^5.0|^6.0", + "symfony/http-foundation": "^5.3.7|^6.0", "symfony/polyfill-ctype": "^1.8", "symfony/polyfill-php73": "^1.9", "symfony/polyfill-php80": "^1.16" }, "conflict": { - "symfony/browser-kit": "<4.3", - "symfony/config": "<3.4", - "symfony/console": ">=5", - "symfony/dependency-injection": "<4.3", - "symfony/translation": "<4.2", - "twig/twig": "<1.43|<2.13,>=2" + "symfony/browser-kit": "<5.4", + "symfony/cache": "<5.0", + "symfony/config": "<5.0", + "symfony/console": "<4.4", + "symfony/dependency-injection": "<5.3", + "symfony/doctrine-bridge": "<5.0", + "symfony/form": "<5.0", + "symfony/http-client": "<5.0", + "symfony/mailer": "<5.0", + "symfony/messenger": "<5.0", + "symfony/translation": "<5.0", + "symfony/twig-bridge": "<5.0", + "symfony/validator": "<5.0", + "twig/twig": "<2.13" }, "provide": { "psr/log-implementation": "1.0|2.0" }, "require-dev": { "psr/cache": "^1.0|^2.0|^3.0", - "symfony/browser-kit": "^4.3|^5.0", - "symfony/config": "^3.4|^4.0|^5.0", - "symfony/console": "^3.4|^4.0", - "symfony/css-selector": "^3.4|^4.0|^5.0", - "symfony/dependency-injection": "^4.3|^5.0", - "symfony/dom-crawler": "^3.4|^4.0|^5.0", - "symfony/expression-language": "^3.4|^4.0|^5.0", - "symfony/finder": "^3.4|^4.0|^5.0", - "symfony/process": "^3.4|^4.0|^5.0", - "symfony/routing": "^3.4|^4.0|^5.0", - "symfony/stopwatch": "^3.4|^4.0|^5.0", - "symfony/templating": "^3.4|^4.0|^5.0", - "symfony/translation": "^4.2|^5.0", - "symfony/translation-contracts": "^1.1|^2", - "twig/twig": "^1.43|^2.13|^3.0.4" + "symfony/browser-kit": "^5.4|^6.0", + "symfony/config": "^5.0|^6.0", + "symfony/console": "^4.4|^5.0|^6.0", + "symfony/css-selector": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^5.3|^6.0", + "symfony/dom-crawler": "^4.4|^5.0|^6.0", + "symfony/expression-language": "^4.4|^5.0|^6.0", + "symfony/finder": "^4.4|^5.0|^6.0", + "symfony/http-client-contracts": "^1.1|^2|^3", + "symfony/process": "^4.4|^5.0|^6.0", + "symfony/routing": "^4.4|^5.0|^6.0", + "symfony/stopwatch": "^4.4|^5.0|^6.0", + "symfony/translation": "^4.4|^5.0|^6.0", + "symfony/translation-contracts": "^1.1|^2|^3", + "twig/twig": "^2.13|^3.0.4" }, "suggest": { "symfony/browser-kit": "", @@ -4605,7 +6681,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v4.4.30" + "source": "https://github.com/symfony/http-kernel/tree/v5.4.1" }, "funding": [ { @@ -4621,25 +6697,25 @@ "type": "tidelift" } ], - "time": "2021-08-30T12:27:20+00:00" + "time": "2021-12-09T13:36:09+00:00" }, { "name": "symfony/mime", - "version": "v5.3.7", + "version": "v5.4.0", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "ae887cb3b044658676129f5e97aeb7e9eb69c2d8" + "reference": "d4365000217b67c01acff407573906ff91bcfb34" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/ae887cb3b044658676129f5e97aeb7e9eb69c2d8", - "reference": "ae887cb3b044658676129f5e97aeb7e9eb69c2d8", + "url": "https://api.github.com/repos/symfony/mime/zipball/d4365000217b67c01acff407573906ff91bcfb34", + "reference": "d4365000217b67c01acff407573906ff91bcfb34", "shasum": "" }, "require": { "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1", + "symfony/deprecation-contracts": "^2.1|^3", "symfony/polyfill-intl-idn": "^1.10", "symfony/polyfill-mbstring": "^1.0", "symfony/polyfill-php80": "^1.16" @@ -4653,10 +6729,10 @@ "require-dev": { "egulias/email-validator": "^2.1.10|^3.1", "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", - "symfony/dependency-injection": "^4.4|^5.0", - "symfony/property-access": "^4.4|^5.1", - "symfony/property-info": "^4.4|^5.1", - "symfony/serializer": "^5.2" + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/property-access": "^4.4|^5.1|^6.0", + "symfony/property-info": "^4.4|^5.1|^6.0", + "symfony/serializer": "^5.2|^6.0" }, "type": "library", "autoload": { @@ -4688,7 +6764,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v5.3.7" + "source": "https://github.com/symfony/mime/tree/v5.4.0" }, "funding": [ { @@ -4704,7 +6780,74 @@ "type": "tidelift" } ], - "time": "2021-08-20T11:40:01+00:00" + "time": "2021-11-23T10:19:22+00:00" + }, + { + "name": "symfony/options-resolver", + "version": "v6.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "be0facf48a42a232d6c0daadd76e4eb5657a4798" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/be0facf48a42a232d6c0daadd76e4eb5657a4798", + "reference": "be0facf48a42a232d6c0daadd76e4eb5657a4798", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/deprecation-contracts": "^2.1|^3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\OptionsResolver\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an improved replacement for the array_replace PHP function", + "homepage": "https://symfony.com", + "keywords": [ + "config", + "configuration", + "options" + ], + "support": { + "source": "https://github.com/symfony/options-resolver/tree/v6.0.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-23T19:05:29+00:00" }, { "name": "symfony/polyfill-ctype", @@ -4785,6 +6928,87 @@ ], "time": "2021-02-19T12:13:01+00:00" }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.23.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "16880ba9c5ebe3642d1995ab866db29270b36535" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/16880ba9c5ebe3642d1995ab866db29270b36535", + "reference": "16880ba9c5ebe3642d1995ab866db29270b36535", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.23.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-05-27T12:26:48+00:00" + }, { "name": "symfony/polyfill-intl-idn", "version": "v1.23.0", @@ -5275,21 +7499,179 @@ "time": "2021-07-28T13:41:28+00:00" }, { - "name": "symfony/process", - "version": "v4.4.30", + "name": "symfony/polyfill-php81", + "version": "v1.23.0", "source": { "type": "git", - "url": "https://github.com/symfony/process.git", - "reference": "13d3161ef63a8ec21eeccaaf9a4d7f784a87a97d" + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "e66119f3de95efc359483f810c4c3e6436279436" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/13d3161ef63a8ec21eeccaaf9a4d7f784a87a97d", - "reference": "13d3161ef63a8ec21eeccaaf9a4d7f784a87a97d", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/e66119f3de95efc359483f810c4c3e6436279436", + "reference": "e66119f3de95efc359483f810c4c3e6436279436", "shasum": "" }, "require": { - "php": ">=7.1.3", + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php81\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php81/tree/v1.23.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-05-21T13:25:03+00:00" + }, + { + "name": "symfony/polyfill-uuid", + "version": "v1.23.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-uuid.git", + "reference": "9165effa2eb8a31bb3fa608df9d529920d21ddd9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/9165effa2eb8a31bb3fa608df9d529920d21ddd9", + "reference": "9165effa2eb8a31bb3fa608df9d529920d21ddd9", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-uuid": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Uuid\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Grégoire Pineau", + "email": "lyrixx@lyrixx.info" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for uuid functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "uuid" + ], + "support": { + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.23.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-02-19T12:13:01+00:00" + }, + { + "name": "symfony/process", + "version": "v5.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "5be20b3830f726e019162b26223110c8f47cf274" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/5be20b3830f726e019162b26223110c8f47cf274", + "reference": "5be20b3830f726e019162b26223110c8f47cf274", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", "symfony/polyfill-php80": "^1.16" }, "type": "library", @@ -5318,7 +7700,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v4.4.30" + "source": "https://github.com/symfony/process/tree/v5.4.0" }, "funding": [ { @@ -5334,26 +7716,117 @@ "type": "tidelift" } ], - "time": "2021-08-04T20:31:23+00:00" + "time": "2021-11-28T15:25:38+00:00" }, { - "name": "symfony/service-contracts", - "version": "v2.4.0", + "name": "symfony/psr-http-message-bridge", + "version": "v2.1.2", "source": { "type": "git", - "url": "https://github.com/symfony/service-contracts.git", - "reference": "f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb" + "url": "https://github.com/symfony/psr-http-message-bridge.git", + "reference": "22b37c8a3f6b5d94e9cdbd88e1270d96e2f97b34" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb", - "reference": "f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb", + "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/22b37c8a3f6b5d94e9cdbd88e1270d96e2f97b34", + "reference": "22b37c8a3f6b5d94e9cdbd88e1270d96e2f97b34", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "psr/http-message": "^1.0", + "symfony/http-foundation": "^4.4 || ^5.0 || ^6.0" + }, + "require-dev": { + "nyholm/psr7": "^1.1", + "psr/log": "^1.1 || ^2 || ^3", + "symfony/browser-kit": "^4.4 || ^5.0 || ^6.0", + "symfony/config": "^4.4 || ^5.0 || ^6.0", + "symfony/event-dispatcher": "^4.4 || ^5.0 || ^6.0", + "symfony/framework-bundle": "^4.4 || ^5.0 || ^6.0", + "symfony/http-kernel": "^4.4 || ^5.0 || ^6.0", + "symfony/phpunit-bridge": "^5.4@dev || ^6.0" + }, + "suggest": { + "nyholm/psr7": "For a super lightweight PSR-7/17 implementation" + }, + "type": "symfony-bridge", + "extra": { + "branch-alias": { + "dev-main": "2.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Bridge\\PsrHttpMessage\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "PSR HTTP message bridge", + "homepage": "http://symfony.com", + "keywords": [ + "http", + "http-message", + "psr-17", + "psr-7" + ], + "support": { + "issues": "https://github.com/symfony/psr-http-message-bridge/issues", + "source": "https://github.com/symfony/psr-http-message-bridge/tree/v2.1.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-05T13:13:39+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v2.4.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "d664541b99d6fb0247ec5ff32e87238582236204" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d664541b99d6fb0247ec5ff32e87238582236204", + "reference": "d664541b99d6fb0247ec5ff32e87238582236204", "shasum": "" }, "require": { "php": ">=7.2.5", "psr/container": "^1.1" }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, "suggest": { "symfony/service-implementation": "" }, @@ -5397,7 +7870,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v2.4.0" + "source": "https://github.com/symfony/service-contracts/tree/v2.4.1" }, "funding": [ { @@ -5413,47 +7886,137 @@ "type": "tidelift" } ], - "time": "2021-04-01T10:43:52+00:00" + "time": "2021-11-04T16:37:19+00:00" }, { - "name": "symfony/translation", - "version": "v4.4.30", + "name": "symfony/string", + "version": "v6.0.1", "source": { "type": "git", - "url": "https://github.com/symfony/translation.git", - "reference": "db0ba1e85280d8ff11e38d53c70f8814d4d740f5" + "url": "https://github.com/symfony/string.git", + "reference": "0cfed595758ec6e0a25591bdc8ca733c1896af32" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/db0ba1e85280d8ff11e38d53c70f8814d4d740f5", - "reference": "db0ba1e85280d8ff11e38d53c70f8814d4d740f5", + "url": "https://api.github.com/repos/symfony/string/zipball/0cfed595758ec6e0a25591bdc8ca733c1896af32", + "reference": "0cfed595758ec6e0a25591bdc8ca733c1896af32", "shasum": "" }, "require": { - "php": ">=7.1.3", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "^1.16", - "symfony/translation-contracts": "^1.1.6|^2" + "php": ">=8.0.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" }, "conflict": { - "symfony/config": "<3.4", - "symfony/dependency-injection": "<3.4", - "symfony/http-kernel": "<4.4", - "symfony/yaml": "<3.4" + "symfony/translation-contracts": "<2.0" + }, + "require-dev": { + "symfony/error-handler": "^5.4|^6.0", + "symfony/http-client": "^5.4|^6.0", + "symfony/translation-contracts": "^2.0|^3.0", + "symfony/var-exporter": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "files": [ + "Resources/functions.php" + ], + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v6.0.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-12-08T15:13:44+00:00" + }, + { + "name": "symfony/translation", + "version": "v5.4.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "8c82cd35ed861236138d5ae1c78c0c7ebcd62107" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/8c82cd35ed861236138d5ae1c78c0c7ebcd62107", + "reference": "8c82cd35ed861236138d5ae1c78c0c7ebcd62107", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "^1.16", + "symfony/translation-contracts": "^2.3" + }, + "conflict": { + "symfony/config": "<4.4", + "symfony/console": "<5.3", + "symfony/dependency-injection": "<5.0", + "symfony/http-kernel": "<5.0", + "symfony/twig-bundle": "<5.0", + "symfony/yaml": "<4.4" }, "provide": { - "symfony/translation-implementation": "1.0|2.0" + "symfony/translation-implementation": "2.3" }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/config": "^3.4|^4.0|^5.0", - "symfony/console": "^3.4|^4.0|^5.0", - "symfony/dependency-injection": "^3.4|^4.0|^5.0", - "symfony/finder": "~2.8|~3.0|~4.0|^5.0", - "symfony/http-kernel": "^4.4", - "symfony/intl": "^3.4|^4.0|^5.0", - "symfony/service-contracts": "^1.1.2|^2", - "symfony/yaml": "^3.4|^4.0|^5.0" + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/console": "^5.4|^6.0", + "symfony/dependency-injection": "^5.0|^6.0", + "symfony/finder": "^4.4|^5.0|^6.0", + "symfony/http-client-contracts": "^1.1|^2.0|^3.0", + "symfony/http-kernel": "^5.0|^6.0", + "symfony/intl": "^4.4|^5.0|^6.0", + "symfony/polyfill-intl-icu": "^1.21", + "symfony/service-contracts": "^1.1.2|^2|^3", + "symfony/yaml": "^4.4|^5.0|^6.0" }, "suggest": { "psr/log-implementation": "To use logging capability in translator", @@ -5462,6 +8025,9 @@ }, "type": "library", "autoload": { + "files": [ + "Resources/functions.php" + ], "psr-4": { "Symfony\\Component\\Translation\\": "" }, @@ -5486,7 +8052,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v4.4.30" + "source": "https://github.com/symfony/translation/tree/v5.4.1" }, "funding": [ { @@ -5502,20 +8068,20 @@ "type": "tidelift" } ], - "time": "2021-08-26T05:57:13+00:00" + "time": "2021-12-05T20:33:52+00:00" }, { "name": "symfony/translation-contracts", - "version": "v2.4.0", + "version": "v2.5.0", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "95c812666f3e91db75385749fe219c5e494c7f95" + "reference": "d28150f0f44ce854e942b671fc2620a98aae1b1e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/95c812666f3e91db75385749fe219c5e494c7f95", - "reference": "95c812666f3e91db75385749fe219c5e494c7f95", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/d28150f0f44ce854e942b671fc2620a98aae1b1e", + "reference": "d28150f0f44ce854e942b671fc2620a98aae1b1e", "shasum": "" }, "require": { @@ -5527,7 +8093,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "2.4-dev" + "dev-main": "2.5-dev" }, "thanks": { "name": "symfony/contracts", @@ -5564,7 +8130,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v2.4.0" + "source": "https://github.com/symfony/translation-contracts/tree/v2.5.0" }, "funding": [ { @@ -5580,37 +8146,37 @@ "type": "tidelift" } ], - "time": "2021-03-23T23:28:01+00:00" + "time": "2021-08-17T14:20:01+00:00" }, { "name": "symfony/var-dumper", - "version": "v4.4.30", + "version": "v5.4.1", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "7f65c44c2ce80d3a0fcdb6385ee0ad535e45660c" + "reference": "2366ac8d8abe0c077844613c1a4f0c0a9f522dcc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/7f65c44c2ce80d3a0fcdb6385ee0ad535e45660c", - "reference": "7f65c44c2ce80d3a0fcdb6385ee0ad535e45660c", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/2366ac8d8abe0c077844613c1a4f0c0a9f522dcc", + "reference": "2366ac8d8abe0c077844613c1a4f0c0a9f522dcc", "shasum": "" }, "require": { - "php": ">=7.1.3", + "php": ">=7.2.5", "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php72": "~1.5", "symfony/polyfill-php80": "^1.16" }, "conflict": { - "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", - "symfony/console": "<3.4" + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<4.4" }, "require-dev": { "ext-iconv": "*", - "symfony/console": "^3.4|^4.0|^5.0", - "symfony/process": "^4.4|^5.0", - "twig/twig": "^1.43|^2.13|^3.0.4" + "symfony/console": "^4.4|^5.0|^6.0", + "symfony/process": "^4.4|^5.0|^6.0", + "symfony/uid": "^5.1|^6.0", + "twig/twig": "^2.13|^3.0.4" }, "suggest": { "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", @@ -5653,7 +8219,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v4.4.30" + "source": "https://github.com/symfony/var-dumper/tree/v5.4.1" }, "funding": [ { @@ -5669,20 +8235,20 @@ "type": "tidelift" } ], - "time": "2021-08-04T20:31:23+00:00" + "time": "2021-12-01T15:04:08+00:00" }, { "name": "symfony/yaml", - "version": "v4.4.29", + "version": "v4.4.34", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "3abcc4db06d4e776825eaa3ed8ad924d5bc7432a" + "reference": "2c309e258adeb9970229042be39b360d34986fad" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/3abcc4db06d4e776825eaa3ed8ad924d5bc7432a", - "reference": "3abcc4db06d4e776825eaa3ed8ad924d5bc7432a", + "url": "https://api.github.com/repos/symfony/yaml/zipball/2c309e258adeb9970229042be39b360d34986fad", + "reference": "2c309e258adeb9970229042be39b360d34986fad", "shasum": "" }, "require": { @@ -5724,7 +8290,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v4.4.29" + "source": "https://github.com/symfony/yaml/tree/v4.4.34" }, "funding": [ { @@ -5740,40 +8306,43 @@ "type": "tidelift" } ], - "time": "2021-07-27T16:19:30+00:00" + "time": "2021-11-18T18:49:23+00:00" }, { "name": "vlucas/phpdotenv", - "version": "v3.6.8", + "version": "v5.4.1", "source": { "type": "git", "url": "https://github.com/vlucas/phpdotenv.git", - "reference": "5e679f7616db829358341e2d5cccbd18773bdab8" + "reference": "264dce589e7ce37a7ba99cb901eed8249fbec92f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/5e679f7616db829358341e2d5cccbd18773bdab8", - "reference": "5e679f7616db829358341e2d5cccbd18773bdab8", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/264dce589e7ce37a7ba99cb901eed8249fbec92f", + "reference": "264dce589e7ce37a7ba99cb901eed8249fbec92f", "shasum": "" }, "require": { - "php": "^5.4 || ^7.0 || ^8.0", - "phpoption/phpoption": "^1.5.2", - "symfony/polyfill-ctype": "^1.17" + "ext-pcre": "*", + "graham-campbell/result-type": "^1.0.2", + "php": "^7.1.3 || ^8.0", + "phpoption/phpoption": "^1.8", + "symfony/polyfill-ctype": "^1.23", + "symfony/polyfill-mbstring": "^1.23.1", + "symfony/polyfill-php80": "^1.23.1" }, "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", "ext-filter": "*", - "ext-pcre": "*", - "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20" + "phpunit/phpunit": "^7.5.20 || ^8.5.21 || ^9.5.10" }, "suggest": { - "ext-filter": "Required to use the boolean validator.", - "ext-pcre": "Required to use most of the library." + "ext-filter": "Required to use the boolean validator." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.6-dev" + "dev-master": "5.4-dev" } }, "autoload": { @@ -5788,13 +8357,13 @@ "authors": [ { "name": "Graham Campbell", - "email": "graham@alt-three.com", - "homepage": "https://gjcampbell.co.uk/" + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" }, { "name": "Vance Lucas", "email": "vance@vancelucas.com", - "homepage": "https://vancelucas.com/" + "homepage": "https://github.com/vlucas" } ], "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", @@ -5805,7 +8374,7 @@ ], "support": { "issues": "https://github.com/vlucas/phpdotenv/issues", - "source": "https://github.com/vlucas/phpdotenv/tree/v3.6.8" + "source": "https://github.com/vlucas/phpdotenv/tree/v5.4.1" }, "funding": [ { @@ -5817,41 +8386,35 @@ "type": "tidelift" } ], - "time": "2021-01-20T14:39:46+00:00" - } - ], - "packages-dev": [ + "time": "2021-12-12T23:22:04+00:00" + }, { - "name": "fzaninotto/faker", - "version": "v1.9.2", + "name": "voku/portable-ascii", + "version": "1.5.6", "source": { "type": "git", - "url": "https://github.com/fzaninotto/Faker.git", - "reference": "848d8125239d7dbf8ab25cb7f054f1a630e68c2e" + "url": "https://github.com/voku/portable-ascii.git", + "reference": "80953678b19901e5165c56752d087fc11526017c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/848d8125239d7dbf8ab25cb7f054f1a630e68c2e", - "reference": "848d8125239d7dbf8ab25cb7f054f1a630e68c2e", + "url": "https://api.github.com/repos/voku/portable-ascii/zipball/80953678b19901e5165c56752d087fc11526017c", + "reference": "80953678b19901e5165c56752d087fc11526017c", "shasum": "" }, "require": { - "php": "^5.3.3 || ^7.0" + "php": ">=7.0.0" }, "require-dev": { - "ext-intl": "*", - "phpunit/phpunit": "^4.8.35 || ^5.7", - "squizlabs/php_codesniffer": "^2.9.2" + "phpunit/phpunit": "~6.0 || ~7.0 || ~9.0" + }, + "suggest": { + "ext-intl": "Use Intl for transliterator_transliterate() support" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.9-dev" - } - }, "autoload": { "psr-4": { - "Faker\\": "src/Faker/" + "voku\\": "src/voku/" } }, "notification-url": "https://packagist.org/downloads/", @@ -5860,38 +8423,194 @@ ], "authors": [ { - "name": "François Zaninotto" + "name": "Lars Moelleken", + "homepage": "http://www.moelleken.org/" } ], - "description": "Faker is a PHP library that generates fake data for you.", + "description": "Portable ASCII library - performance optimized (ascii) string functions for php.", + "homepage": "https://github.com/voku/portable-ascii", "keywords": [ - "data", - "faker", - "fixtures" + "ascii", + "clean", + "php" ], "support": { - "issues": "https://github.com/fzaninotto/Faker/issues", - "source": "https://github.com/fzaninotto/Faker/tree/v1.9.2" + "issues": "https://github.com/voku/portable-ascii/issues", + "source": "https://github.com/voku/portable-ascii/tree/1.5.6" }, - "abandoned": true, - "time": "2020-12-11T09:56:16+00:00" + "funding": [ + { + "url": "https://www.paypal.me/moelleken", + "type": "custom" + }, + { + "url": "https://github.com/voku", + "type": "github" + }, + { + "url": "https://opencollective.com/portable-ascii", + "type": "open_collective" + }, + { + "url": "https://www.patreon.com/voku", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/voku/portable-ascii", + "type": "tidelift" + } + ], + "time": "2020-11-12T00:07:28+00:00" }, { - "name": "hamcrest/hamcrest-php", - "version": "v1.2.2", + "name": "webmozart/assert", + "version": "1.10.0", "source": { "type": "git", - "url": "https://github.com/hamcrest/hamcrest-php.git", - "reference": "b37020aa976fa52d3de9aa904aa2522dc518f79c" + "url": "https://github.com/webmozarts/assert.git", + "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/b37020aa976fa52d3de9aa904aa2522dc518f79c", - "reference": "b37020aa976fa52d3de9aa904aa2522dc518f79c", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/6964c76c7804814a842473e0c8fd15bab0f18e25", + "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25", "shasum": "" }, "require": { - "php": ">=5.3.2" + "php": "^7.2 || ^8.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<4.6.1 || 4.6.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.13" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.10.0" + }, + "time": "2021-03-09T10:59:23+00:00" + }, + { + "name": "zircote/swagger-php", + "version": "3.3.3", + "source": { + "type": "git", + "url": "https://github.com/zircote/swagger-php.git", + "reference": "cec9943f974df43370c51be7c489fc1007f80f2b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zircote/swagger-php/zipball/cec9943f974df43370c51be7c489fc1007f80f2b", + "reference": "cec9943f974df43370c51be7c489fc1007f80f2b", + "shasum": "" + }, + "require": { + "doctrine/annotations": "^1.7", + "ext-json": "*", + "php": ">=7.2", + "psr/log": "^1.1 || ^2.0 || ^3.0", + "symfony/finder": ">=2.2", + "symfony/yaml": ">=3.3" + }, + "require-dev": { + "composer/package-versions-deprecated": "1.11.99.2", + "friendsofphp/php-cs-fixer": "^2.17 || ^3.0", + "phpunit/phpunit": ">=8.5.14" + }, + "bin": [ + "bin/openapi" + ], + "type": "library", + "autoload": { + "psr-4": { + "OpenApi\\": "src" + }, + "files": [ + "src/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Robert Allen", + "email": "zircote@gmail.com" + }, + { + "name": "Bob Fanger", + "email": "bfanger@gmail.com", + "homepage": "https://bfanger.nl" + }, + { + "name": "Martin Rademacher", + "email": "mano@radebatz.net", + "homepage": "https://radebatz.net" + } + ], + "description": "swagger-php - Generate interactive documentation for your RESTful API using phpdoc annotations", + "homepage": "https://github.com/zircote/swagger-php/", + "keywords": [ + "api", + "json", + "rest", + "service discovery" + ], + "support": { + "issues": "https://github.com/zircote/swagger-php/issues", + "source": "https://github.com/zircote/swagger-php/tree/3.3.3" + }, + "time": "2021-12-23T19:45:20+00:00" + } + ], + "packages-dev": [ + { + "name": "hamcrest/hamcrest-php", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/hamcrest/hamcrest-php.git", + "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", + "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", + "shasum": "" + }, + "require": { + "php": "^5.3|^7.0|^8.0" }, "replace": { "cordoval/hamcrest-php": "*", @@ -5899,21 +8618,23 @@ "kodova/hamcrest-php": "*" }, "require-dev": { - "phpunit/php-file-iterator": "1.3.3", - "satooshi/php-coveralls": "dev-master" + "phpunit/php-file-iterator": "^1.4 || ^2.0", + "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, "autoload": { "classmap": [ "hamcrest" - ], - "files": [ - "hamcrest/Hamcrest.php" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD" + "BSD-3-Clause" ], "description": "This is the PHP port of Hamcrest Matchers", "keywords": [ @@ -5921,36 +8642,39 @@ ], "support": { "issues": "https://github.com/hamcrest/hamcrest-php/issues", - "source": "https://github.com/hamcrest/hamcrest-php/tree/master" + "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.0.1" }, - "time": "2015-05-11T14:41:42+00:00" + "time": "2020-07-09T08:09:16+00:00" }, { "name": "mockery/mockery", - "version": "0.9.11", + "version": "1.4.4", "source": { "type": "git", "url": "https://github.com/mockery/mockery.git", - "reference": "be9bf28d8e57d67883cba9fcadfcff8caab667f8" + "reference": "e01123a0e847d52d186c5eb4b9bf58b0c6d00346" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mockery/mockery/zipball/be9bf28d8e57d67883cba9fcadfcff8caab667f8", - "reference": "be9bf28d8e57d67883cba9fcadfcff8caab667f8", + "url": "https://api.github.com/repos/mockery/mockery/zipball/e01123a0e847d52d186c5eb4b9bf58b0c6d00346", + "reference": "e01123a0e847d52d186c5eb4b9bf58b0c6d00346", "shasum": "" }, "require": { - "hamcrest/hamcrest-php": "~1.1", + "hamcrest/hamcrest-php": "^2.0.1", "lib-pcre": ">=7.0", - "php": ">=5.3.2" + "php": "^7.3 || ^8.0" + }, + "conflict": { + "phpunit/phpunit": "<8.0" }, "require-dev": { - "phpunit/phpunit": "~4.0" + "phpunit/phpunit": "^8.5 || ^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "0.9.x-dev" + "dev-master": "1.4.x-dev" } }, "autoload": { @@ -5974,8 +8698,8 @@ "homepage": "http://davedevelopment.co.uk" } ], - "description": "Mockery is a simple yet flexible PHP mock object framework for use in unit testing with PHPUnit, PHPSpec or any other testing framework. Its core goal is to offer a test double framework with a succinct API capable of clearly defining all possible object operations and interactions using a human readable Domain Specific Language (DSL). Designed as a drop in alternative to PHPUnit's phpunit-mock-objects library, Mockery is easy to integrate with PHPUnit and can operate alongside phpunit-mock-objects without the World ending.", - "homepage": "http://github.com/padraic/mockery", + "description": "Mockery is a simple yet flexible PHP mock object framework", + "homepage": "https://github.com/mockery/mockery", "keywords": [ "BDD", "TDD", @@ -5990,9 +8714,9 @@ ], "support": { "issues": "https://github.com/mockery/mockery/issues", - "source": "https://github.com/mockery/mockery/tree/0.9" + "source": "https://github.com/mockery/mockery/tree/1.4.4" }, - "time": "2019-02-12T16:07:13+00:00" + "time": "2021-09-13T15:28:59+00:00" }, { "name": "myclabs/deep-copy", @@ -6011,9 +8735,6 @@ "require": { "php": "^7.1 || ^8.0" }, - "replace": { - "myclabs/deep-copy": "self.version" - }, "require-dev": { "doctrine/collections": "^1.0", "doctrine/common": "^2.6", @@ -6054,28 +8775,29 @@ }, { "name": "phar-io/manifest", - "version": "1.0.3", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/phar-io/manifest.git", - "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4" + "reference": "97803eca37d319dfa7826cc2437fc020857acb53" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", - "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53", "shasum": "" }, "require": { "ext-dom": "*", "ext-phar": "*", - "phar-io/version": "^2.0", - "php": "^5.6 || ^7.0" + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { @@ -6107,26 +8829,26 @@ "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", "support": { "issues": "https://github.com/phar-io/manifest/issues", - "source": "https://github.com/phar-io/manifest/tree/master" + "source": "https://github.com/phar-io/manifest/tree/2.0.3" }, - "time": "2018-07-08T19:23:20+00:00" + "time": "2021-07-20T11:28:43+00:00" }, { "name": "phar-io/version", - "version": "2.0.1", + "version": "3.1.0", "source": { "type": "git", "url": "https://github.com/phar-io/version.git", - "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6" + "reference": "bae7c545bef187884426f042434e561ab1ddb182" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/version/zipball/45a2ec53a73c70ce41d55cedef9063630abaf1b6", - "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6", + "url": "https://api.github.com/repos/phar-io/version/zipball/bae7c545bef187884426f042434e561ab1ddb182", + "reference": "bae7c545bef187884426f042434e561ab1ddb182", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" + "php": "^7.2 || ^8.0" }, "type": "library", "autoload": { @@ -6158,9 +8880,9 @@ "description": "Library for handling version information and constraints", "support": { "issues": "https://github.com/phar-io/version/issues", - "source": "https://github.com/phar-io/version/tree/master" + "source": "https://github.com/phar-io/version/tree/3.1.0" }, - "time": "2018-07-08T19:19:57+00:00" + "time": "2021-02-23T14:00:09+00:00" }, { "name": "phpdocumentor/reflection-common", @@ -6217,16 +8939,16 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "5.2.2", + "version": "5.3.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556" + "reference": "622548b623e81ca6d78b721c5e029f4ce664f170" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/069a785b2141f5bcf49f3e353548dc1cce6df556", - "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170", + "reference": "622548b623e81ca6d78b721c5e029f4ce664f170", "shasum": "" }, "require": { @@ -6237,7 +8959,8 @@ "webmozart/assert": "^1.9.1" }, "require-dev": { - "mockery/mockery": "~1.3.2" + "mockery/mockery": "~1.3.2", + "psalm/phar": "^4.8" }, "type": "library", "extra": { @@ -6267,22 +8990,22 @@ "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", "support": { "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/master" + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.3.0" }, - "time": "2020-09-03T19:13:55+00:00" + "time": "2021-10-19T17:43:47+00:00" }, { "name": "phpdocumentor/type-resolver", - "version": "1.5.0", + "version": "1.5.1", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "30f38bffc6f24293dadd1823936372dfa9e86e2f" + "reference": "a12f7e301eb7258bb68acd89d4aefa05c2906cae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/30f38bffc6f24293dadd1823936372dfa9e86e2f", - "reference": "30f38bffc6f24293dadd1823936372dfa9e86e2f", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/a12f7e301eb7258bb68acd89d4aefa05c2906cae", + "reference": "a12f7e301eb7258bb68acd89d4aefa05c2906cae", "shasum": "" }, "require": { @@ -6317,22 +9040,22 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.5.0" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.5.1" }, - "time": "2021-09-17T15:28:14+00:00" + "time": "2021-10-02T14:08:47+00:00" }, { "name": "phpspec/prophecy", - "version": "1.14.0", + "version": "v1.15.0", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e" + "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e", - "reference": "d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/bbcd7380b0ebf3961ee21409db7b38bc31d69a13", + "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13", "shasum": "" }, "require": { @@ -6384,46 +9107,46 @@ ], "support": { "issues": "https://github.com/phpspec/prophecy/issues", - "source": "https://github.com/phpspec/prophecy/tree/1.14.0" + "source": "https://github.com/phpspec/prophecy/tree/v1.15.0" }, - "time": "2021-09-10T09:02:12+00:00" + "time": "2021-12-08T12:19:24+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "6.1.4", + "version": "7.0.15", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d" + "reference": "819f92bba8b001d4363065928088de22f25a3a48" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/807e6013b00af69b6c5d9ceb4282d0393dbb9d8d", - "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/819f92bba8b001d4363065928088de22f25a3a48", + "reference": "819f92bba8b001d4363065928088de22f25a3a48", "shasum": "" }, "require": { "ext-dom": "*", "ext-xmlwriter": "*", - "php": "^7.1", - "phpunit/php-file-iterator": "^2.0", + "php": ">=7.2", + "phpunit/php-file-iterator": "^2.0.2", "phpunit/php-text-template": "^1.2.1", - "phpunit/php-token-stream": "^3.0", + "phpunit/php-token-stream": "^3.1.3 || ^4.0", "sebastian/code-unit-reverse-lookup": "^1.0.1", - "sebastian/environment": "^3.1 || ^4.0", + "sebastian/environment": "^4.2.2", "sebastian/version": "^2.0.1", - "theseer/tokenizer": "^1.1" + "theseer/tokenizer": "^1.1.3" }, "require-dev": { - "phpunit/phpunit": "^7.0" + "phpunit/phpunit": "^8.2.2" }, "suggest": { - "ext-xdebug": "^2.6.0" + "ext-xdebug": "^2.7.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "6.1-dev" + "dev-master": "7.0-dev" } }, "autoload": { @@ -6451,22 +9174,28 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/master" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/7.0.15" }, - "time": "2018-10-31T16:06:48+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-07-26T12:20:09+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "2.0.4", + "version": "2.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "28af674ff175d0768a5a978e6de83f697d4a7f05" + "reference": "42c5ba5220e6904cbfe8b1a1bda7c0cfdc8c12f5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/28af674ff175d0768a5a978e6de83f697d4a7f05", - "reference": "28af674ff175d0768a5a978e6de83f697d4a7f05", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/42c5ba5220e6904cbfe8b1a1bda7c0cfdc8c12f5", + "reference": "42c5ba5220e6904cbfe8b1a1bda7c0cfdc8c12f5", "shasum": "" }, "require": { @@ -6505,7 +9234,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/2.0.4" + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/2.0.5" }, "funding": [ { @@ -6513,7 +9242,7 @@ "type": "github" } ], - "time": "2021-07-19T06:46:01+00:00" + "time": "2021-12-02T12:42:26+00:00" }, { "name": "phpunit/php-text-template", @@ -6621,29 +9350,29 @@ }, { "name": "phpunit/php-token-stream", - "version": "3.1.3", + "version": "4.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "9c1da83261628cb24b6a6df371b6e312b3954768" + "reference": "a853a0e183b9db7eed023d7933a858fa1c8d25a3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/9c1da83261628cb24b6a6df371b6e312b3954768", - "reference": "9c1da83261628cb24b6a6df371b6e312b3954768", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/a853a0e183b9db7eed023d7933a858fa1c8d25a3", + "reference": "a853a0e183b9db7eed023d7933a858fa1c8d25a3", "shasum": "" }, "require": { "ext-tokenizer": "*", - "php": ">=7.1" + "php": "^7.3 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "^7.0" + "phpunit/phpunit": "^9.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -6668,7 +9397,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-token-stream/issues", - "source": "https://github.com/sebastianbergmann/php-token-stream/tree/3.1.3" + "source": "https://github.com/sebastianbergmann/php-token-stream/tree/master" }, "funding": [ { @@ -6677,57 +9406,56 @@ } ], "abandoned": true, - "time": "2021-07-26T12:15:06+00:00" + "time": "2020-08-04T08:28:15+00:00" }, { "name": "phpunit/phpunit", - "version": "7.5.20", + "version": "8.5.22", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "9467db479d1b0487c99733bb1e7944d32deded2c" + "reference": "ddd05b9d844260353895a3b950a9258126c11503" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9467db479d1b0487c99733bb1e7944d32deded2c", - "reference": "9467db479d1b0487c99733bb1e7944d32deded2c", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/ddd05b9d844260353895a3b950a9258126c11503", + "reference": "ddd05b9d844260353895a3b950a9258126c11503", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.1", + "doctrine/instantiator": "^1.3.1", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", "ext-mbstring": "*", "ext-xml": "*", - "myclabs/deep-copy": "^1.7", - "phar-io/manifest": "^1.0.2", - "phar-io/version": "^2.0", - "php": "^7.1", - "phpspec/prophecy": "^1.7", - "phpunit/php-code-coverage": "^6.0.7", - "phpunit/php-file-iterator": "^2.0.1", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.10.0", + "phar-io/manifest": "^2.0.3", + "phar-io/version": "^3.0.2", + "php": ">=7.2", + "phpspec/prophecy": "^1.10.3", + "phpunit/php-code-coverage": "^7.0.12", + "phpunit/php-file-iterator": "^2.0.4", "phpunit/php-text-template": "^1.2.1", - "phpunit/php-timer": "^2.1", - "sebastian/comparator": "^3.0", - "sebastian/diff": "^3.0", - "sebastian/environment": "^4.0", - "sebastian/exporter": "^3.1", - "sebastian/global-state": "^2.0", + "phpunit/php-timer": "^2.1.2", + "sebastian/comparator": "^3.0.2", + "sebastian/diff": "^3.0.2", + "sebastian/environment": "^4.2.3", + "sebastian/exporter": "^3.1.2", + "sebastian/global-state": "^3.0.0", "sebastian/object-enumerator": "^3.0.3", - "sebastian/resource-operations": "^2.0", + "sebastian/resource-operations": "^2.0.1", + "sebastian/type": "^1.1.3", "sebastian/version": "^2.0.1" }, - "conflict": { - "phpunit/phpunit-mock-objects": "*" - }, "require-dev": { "ext-pdo": "*" }, "suggest": { "ext-soap": "*", "ext-xdebug": "*", - "phpunit/php-invoker": "^2.0" + "phpunit/php-invoker": "^2.0.0" }, "bin": [ "phpunit" @@ -6735,7 +9463,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "7.5-dev" + "dev-master": "8.5-dev" } }, "autoload": { @@ -6763,9 +9491,19 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/7.5.20" + "source": "https://github.com/sebastianbergmann/phpunit/tree/8.5.22" }, - "time": "2020-01-08T08:45:45+00:00" + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-12-25T06:58:09+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", @@ -7027,16 +9765,16 @@ }, { "name": "sebastian/exporter", - "version": "3.1.3", + "version": "3.1.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "6b853149eab67d4da22291d36f5b0631c0fd856e" + "reference": "0c32ea2e40dbf59de29f3b49bf375176ce7dd8db" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/6b853149eab67d4da22291d36f5b0631c0fd856e", - "reference": "6b853149eab67d4da22291d36f5b0631c0fd856e", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/0c32ea2e40dbf59de29f3b49bf375176ce7dd8db", + "reference": "0c32ea2e40dbf59de29f3b49bf375176ce7dd8db", "shasum": "" }, "require": { @@ -7045,7 +9783,7 @@ }, "require-dev": { "ext-mbstring": "*", - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^8.5" }, "type": "library", "extra": { @@ -7092,7 +9830,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/3.1.3" + "source": "https://github.com/sebastianbergmann/exporter/tree/3.1.4" }, "funding": [ { @@ -7100,27 +9838,30 @@ "type": "github" } ], - "time": "2020-11-30T07:47:53+00:00" + "time": "2021-11-11T13:51:24+00:00" }, { "name": "sebastian/global-state", - "version": "2.0.0", + "version": "3.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4" + "reference": "474fb9edb7ab891665d3bfc6317f42a0a150454b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", - "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/474fb9edb7ab891665d3bfc6317f42a0a150454b", + "reference": "474fb9edb7ab891665d3bfc6317f42a0a150454b", "shasum": "" }, "require": { - "php": "^7.0" + "php": ">=7.2", + "sebastian/object-reflector": "^1.1.1", + "sebastian/recursion-context": "^3.0" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "ext-dom": "*", + "phpunit/phpunit": "^8.0" }, "suggest": { "ext-uopz": "*" @@ -7128,7 +9869,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -7153,9 +9894,15 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/2.0.0" + "source": "https://github.com/sebastianbergmann/global-state/tree/3.0.1" }, - "time": "2017-04-27T15:39:26+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T07:43:24+00:00" }, { "name": "sebastian/object-enumerator", @@ -7384,6 +10131,62 @@ ], "time": "2020-11-30T07:30:19+00:00" }, + { + "name": "sebastian/type", + "version": "1.1.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "0150cfbc4495ed2df3872fb31b26781e4e077eb4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/0150cfbc4495ed2df3872fb31b26781e4e077eb4", + "reference": "0150cfbc4495ed2df3872fb31b26781e4e077eb4", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/1.1.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T07:25:11+00:00" + }, { "name": "sebastian/version", "version": "2.0.1", @@ -7480,77 +10283,21 @@ } ], "time": "2021-07-28T10:34:58+00:00" - }, - { - "name": "webmozart/assert", - "version": "1.10.0", - "source": { - "type": "git", - "url": "https://github.com/webmozarts/assert.git", - "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/6964c76c7804814a842473e0c8fd15bab0f18e25", - "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0", - "symfony/polyfill-ctype": "^1.8" - }, - "conflict": { - "phpstan/phpstan": "<0.12.20", - "vimeo/psalm": "<4.6.1 || 4.6.2" - }, - "require-dev": { - "phpunit/phpunit": "^8.5.13" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.10-dev" - } - }, - "autoload": { - "psr-4": { - "Webmozart\\Assert\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - } - ], - "description": "Assertions to validate method input/output with nice error messages.", - "keywords": [ - "assert", - "check", - "validate" - ], - "support": { - "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/1.10.0" - }, - "time": "2021-03-09T10:59:23+00:00" } ], "aliases": [], "minimum-stability": "dev", "stability-flags": { - "danielmewes/php-rql": 20 + "danielmewes/php-rql": 20, + "jikan-me/jikan": 20 }, "prefer-stable": true, "prefer-lowest": false, "platform": { - "php": ">=7.1", - "ext-json": "*" + "php": "^7.4|^8.0", + "ext-json": "*", + "ext-mongodb": "*" }, "platform-dev": [], - "plugin-api-version": "2.1.0" + "plugin-api-version": "2.2.0" } diff --git a/conf/supervisor/jikan-worker.conf b/conf/supervisor/jikan-worker.conf deleted file mode 100755 index f3e204e..0000000 --- a/conf/supervisor/jikan-worker.conf +++ /dev/null @@ -1,12 +0,0 @@ -[program:jikan-worker] -process_name=%(program_name)s_%(process_num)02d -command=php /var/www/jikan-rest/artisan queue:work --queue=high,low --tries=3 -autostart=true -autorestart=true -;user=forge -; Number of active jikan workers, you can change this -numprocs=3 -;redirect_stderr=true -; Feel free to comment out `stdout_logfile`, it's just for debugging purposes -stdout_logfile=/var/www/jikan-rest/storage/logs/worker.log -stderr_logfile=/var/www/jikan-rest/storage/logs/worker.error.log diff --git a/config/controller.php b/config/controller.php new file mode 100644 index 0000000..8a8f306 --- /dev/null +++ b/config/controller.php @@ -0,0 +1,381 @@ + [ + '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') + ], + + /** + * Manga + */ + 'MangaController@main' => [ + 'table_name' => 'manga', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + 'MangaController@characters' => [ + 'table_name' => 'manga_characters', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + 'MangaController@news' => [ + 'table_name' => 'manga_news', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + 'MangaController@forum' => [ + 'table_name' => 'manga_news', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + 'MangaController@pictures' => [ + 'table_name' => 'manga_pictures', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + 'MangaController@stats' => [ + 'table_name' => 'manga_stats', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + 'MangaController@moreInfo' => [ + 'table_name' => 'manga_moreinfo', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + 'MangaController@recommendations' => [ + 'table_name' => 'manga_recommendations', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + 'MangaController@userupdates' => [ + 'table_name' => 'manga_userupdates', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + 'MangaController@reviews' => [ + 'table_name' => 'manga_reviews', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + + /** + * Characters + */ + 'CharacterController@main' => [ + 'table_name' => 'characters', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + 'CharacterController@anime' => [ + 'table_name' => 'characters', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + 'CharacterController@manga' => [ + 'table_name' => 'characters', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + 'CharacterController@voices' => [ + 'table_name' => 'characters', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + 'CharacterController@pictures' => [ + 'table_name' => 'characters_pictures', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + + /** + * Person + */ + 'PersonController@main' => [ + 'table_name' => 'people', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + 'PersonController@anime' => [ + 'table_name' => 'people', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + 'PersonController@manga' => [ + 'table_name' => 'people', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + 'PersonController@seiyuu' => [ + 'table_name' => 'people', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + 'PersonController@pictures' => [ + 'table_name' => 'people_pictures', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + + /** + * Season + */ + 'SeasonController@archive' => [ + 'table_name' => 'season_archive', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + 'SeasonController@later' => [ + 'table_name' => 'season_later', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + 'SeasonController@main' => [ + 'table_name' => 'season', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + + /** + * Schedule + */ + 'ScheduleController@main' => [ + 'table_name' => 'schedule', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + + /** + * Producers + */ + 'ProducerController@main' => [ + 'table_name' => 'common', + 'ttl' => env('CACHE_PRODUCERS_EXPIRE') + ], + 'ProducerController@resource' => [ + 'table_name' => 'common', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + + /** + * Magazines + */ + 'MagazineController@main' => [ + 'table_name' => 'common', + 'ttl' => env('CACHE_MAGAZINE_EXPIRE') + ], + 'MagazineController@resource' => [ + 'table_name' => 'common', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + + /** + * Users + */ + 'UserController@recentlyOnline' => [ + 'table_name' => 'users_recently_online', + 'ttl' => env('CACHE_USERS_RECENTLY_ONLINE') + ], + 'UserController@profile' => [ + 'table_name' => 'users', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + 'UserController@statistics' => [ + 'table_name' => 'users', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + 'UserController@favorites' => [ + 'table_name' => 'users', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + 'UserController@about' => [ + 'table_name' => 'users', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + 'UserController@history' => [ + 'table_name' => 'users_history', + 'ttl' => env('CACHE_USER_EXPIRE') + ], + 'UserController@friends' => [ + 'table_name' => 'users_friends', + 'ttl' => env('CACHE_USER_EXPIRE') + ], + 'UserController@recommendations' => [ + 'table_name' => 'users_recommendations', + 'ttl' => env('CACHE_USER_EXPIRE') + ], + 'UserController@reviews' => [ + 'table_name' => 'users_reviews', + 'ttl' => env('CACHE_USER_EXPIRE') + ], + 'UserController@clubs' => [ + 'table_name' => 'users_clubs', + 'ttl' => env('CACHE_USER_EXPIRE') + ], + + /** + * User Lists + */ + 'UserController@animelist' => [ + 'table_name' => 'users_animelist', + 'ttl' => env('CACHE_USERLIST_EXPIRE') + ], + 'UserController@mangalist' => [ + 'table_name' => 'users_mangalist', + 'ttl' => env('CACHE_USERLIST_EXPIRE') + ], + + /** + * Genre + */ + 'GenreController@mainAnime' => [ + 'table_name' => 'common', + 'ttl' => env('CACHE_GENRE_EXPIRE') + ], + 'GenreController@mainManga' => [ + 'table_name' => 'common', + 'ttl' => env('CACHE_GENRE_EXPIRE') + ], + 'GenreController@anime' => [ + 'table_name' => 'common', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + 'GenreController@manga' => [ + 'table_name' => 'common', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + + /** + * Top + */ + 'TopController@anime' => [ + 'table_name' => 'common', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + 'TopController@manga' => [ + 'table_name' => 'common', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + 'TopController@characters' => [ + 'table_name' => 'common', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + 'TopController@people' => [ + 'table_name' => 'common', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + 'TopController@reviews' => [ + 'table_name' => 'common', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + + /** + * Search + */ + 'SearchController@anime' => [ + 'table_name' => 'common', + 'ttl' => env('CACHE_SEARCH_EXPIRE') + ], + 'SearchController@manga' => [ + 'table_name' => 'common', + 'ttl' => env('CACHE_SEARCH_EXPIRE') + ], + 'SearchController@character' => [ + 'table_name' => 'common', + 'ttl' => env('CACHE_SEARCH_EXPIRE') + ], + 'SearchController@people' => [ + 'table_name' => 'common', + 'ttl' => env('CACHE_SEARCH_EXPIRE') + ], + 'SearchController@users' => [ + 'table_name' => 'common', + 'ttl' => env('CACHE_SEARCH_EXPIRE') + ], + 'SearchController@userById' => [ + 'table_name' => 'common', + 'ttl' => env('CACHE_SEARCH_EXPIRE') + ], + + 'ClubController@main' => [ + 'table_name' => 'clubs', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + 'ClubController@members' => [ + 'table_name' => 'clubs_members', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + + 'ReviewsController@anime' => [ + 'table_name' => 'reviews', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + 'ReviewsController@manga' => [ + 'table_name' => 'reviews', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + + 'RecommendationsController@anime' => [ + 'table_name' => 'recommendations', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + 'RecommendationsController@manga' => [ + 'table_name' => 'recommendations', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + + 'WatchController@recentEpisodes' => [ + 'table_name' => 'watch', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + 'WatchController@popularEpisodes' => [ + 'table_name' => 'watch', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + 'WatchController@recentPromos' => [ + 'table_name' => 'watch', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + 'WatchController@popularPromos' => [ + 'table_name' => 'watch', + 'ttl' => env('CACHE_DEFAULT_EXPIRE') + ], + +]; \ No newline at end of file diff --git a/config/database.php b/config/database.php index ed6bf12..e3b278c 100755 --- a/config/database.php +++ b/config/database.php @@ -1,6 +1,16 @@ env('DB_CONNECTION', 'mongodb'), + + 'connections' => [ + 'mongodb' => [ + 'driver' => 'mongodb', + 'dsn'=> "mongodb://".env('DB_USERNAME', 'admin').":".env('DB_PASSWORD', '')."@".env('DB_HOST', 'localhost').":".env('DB_PORT', 27017)."/".env('DB_ADMIN', 'admin'), + 'database' => env('DB_DATABASE', 'jikan'), + ] + ], + 'redis' => [ 'client' => 'predis', 'default' => [ @@ -9,5 +19,7 @@ return [ 'port' => env('REDIS_PORT', 6379), 'database' => 0 ] - ] + ], + + 'migrations' => 'migrations' ]; diff --git a/config/queue.php b/config/queue.php index 2b38412..a076034 100755 --- a/config/queue.php +++ b/config/queue.php @@ -15,7 +15,7 @@ return [ | */ - 'default' => env('QUEUE_CONNECTION', 'redis'), + 'default' => env('QUEUE_CONNECTION', 'mongodb'), /* |-------------------------------------------------------------------------- @@ -35,10 +35,12 @@ return [ ], 'database' => [ - 'driver' => 'database', - 'table' => 'jobs', - 'queue' => 'default', - 'retry_after' => 60, + 'driver' => 'mongodb', + 'connection' => 'mongodb', + 'dsn'=> "mongodb+srv://".env('DB_USERNAME', 'jikan').":".env('DB_PASSWORD', '')."@".env('MONGODB_DSN', ''), + 'table' => env('QUEUE_TABLE', 'jobs'), + 'queue' => 'low', +// 'retry_after' => 60, ], 'beanstalkd' => [ @@ -78,7 +80,8 @@ return [ */ 'failed' => [ - 'database' => 'mysql', + 'driver' => env('QUEUE_CONNECTION', 'mongodb'), + 'database' => env('DB_DATABASE', 'jikan'), 'table' => env('QUEUE_FAILED_TABLE', 'failed_jobs'), ], diff --git a/config/sentry.php b/config/sentry.php new file mode 100644 index 0000000..0f2c2e1 --- /dev/null +++ b/config/sentry.php @@ -0,0 +1,57 @@ + env('SENTRY_LARAVEL_DSN', env('SENTRY_DSN')), + + // capture release as git sha + // 'release' => trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD')), + + // When left empty or `null` the Laravel environment will be used + 'environment' => env('SENTRY_ENVIRONMENT'), + + 'breadcrumbs' => [ + // Capture Laravel logs in breadcrumbs + 'logs' => true, + + // Capture SQL queries in breadcrumbs + 'sql_queries' => true, + + // Capture bindings on SQL queries logged in breadcrumbs + 'sql_bindings' => true, + + // Capture queue job information in breadcrumbs + 'queue_info' => true, + + // Capture command information in breadcrumbs + 'command_info' => true, + ], + + 'tracing' => [ + // Trace queue jobs as their own transactions + 'queue_job_transactions' => env('SENTRY_TRACE_QUEUE_ENABLED', false), + + // Capture queue jobs as spans when executed on the sync driver + 'queue_jobs' => true, + + // Capture SQL queries as spans + 'sql_queries' => true, + + // Try to find out where the SQL query originated from and add it to the query spans + 'sql_origin' => true, + + // Capture views as spans + 'views' => true, + + // Indicates if the tracing integrations supplied by Sentry should be loaded + 'default_integrations' => true, + ], + + // @see: https://docs.sentry.io/platforms/php/configuration/options/#send-default-pii + 'send_default_pii' => false, + + 'traces_sample_rate' => (float)(env('SENTRY_TRACES_SAMPLE_RATE', 0.0)), + + 'controllers_base_namespace' => env('SENTRY_CONTROLLERS_BASE_NAMESPACE', 'App\\Http\\Controllers'), + +]; diff --git a/config/swagger-lume.php b/config/swagger-lume.php new file mode 100644 index 0000000..5e9bb9d --- /dev/null +++ b/config/swagger-lume.php @@ -0,0 +1,305 @@ + [ + /* + |-------------------------------------------------------------------------- + | Edit to set the api's title + |-------------------------------------------------------------------------- + */ + 'title' => 'Swagger Lume API', + ], + + 'routes' => [ + /* + |-------------------------------------------------------------------------- + | Route for accessing api documentation interface + |-------------------------------------------------------------------------- + */ + 'api' => '/api/documentation', + + /* + |-------------------------------------------------------------------------- + | Route for accessing parsed swagger annotations. + |-------------------------------------------------------------------------- + */ + 'docs' => '/docs', + + /* + |-------------------------------------------------------------------------- + | Route for Oauth2 authentication callback. + |-------------------------------------------------------------------------- + */ + 'oauth2_callback' => '/api/oauth2-callback', + + /* + |-------------------------------------------------------------------------- + | Route for serving assets + |-------------------------------------------------------------------------- + */ + 'assets' => '/swagger-ui-assets', + + /* + |-------------------------------------------------------------------------- + | Middleware allows to prevent unexpected access to API documentation + |-------------------------------------------------------------------------- + */ + 'middleware' => [ + 'api' => [], + 'asset' => [], + 'docs' => [], + 'oauth2_callback' => [], + ], + ], + + 'paths' => [ + /* + |-------------------------------------------------------------------------- + | Absolute path to location where parsed swagger annotations will be stored + |-------------------------------------------------------------------------- + */ + 'docs' => storage_path('api-docs'), + + /* + |-------------------------------------------------------------------------- + | File name of the generated json documentation file + |-------------------------------------------------------------------------- + */ + 'docs_json' => 'api-docs.json', + + /* + |-------------------------------------------------------------------------- + | Absolute path to directory containing the swagger annotations are stored. + |-------------------------------------------------------------------------- + */ + 'annotations' => base_path('app'), + + /* + |-------------------------------------------------------------------------- + | Absolute path to directories that you would like to exclude from swagger generation + |-------------------------------------------------------------------------- + */ + 'excludes' => [], + + /* + |-------------------------------------------------------------------------- + | Edit to set the swagger scan base path + |-------------------------------------------------------------------------- + */ + 'base' => env('L5_SWAGGER_BASE_PATH', null), + + /* + |-------------------------------------------------------------------------- + | Absolute path to directory where to export views + |-------------------------------------------------------------------------- + */ + 'views' => base_path('resources/views/vendor/swagger-lume'), + ], + + /* + |-------------------------------------------------------------------------- + | API security definitions. Will be generated into documentation file. + |-------------------------------------------------------------------------- + */ + 'security' => [ + /* + |-------------------------------------------------------------------------- + | Examples of Security definitions + |-------------------------------------------------------------------------- + */ + /* + 'api_key_security_example' => [ // Unique name of security + 'type' => 'apiKey', // The type of the security scheme. Valid values are "basic", "apiKey" or "oauth2". + 'description' => 'A short description for security scheme', + 'name' => 'api_key', // The name of the header or query parameter to be used. + 'in' => 'header', // The location of the API key. Valid values are "query" or "header". + ], + 'oauth2_security_example' => [ // Unique name of security + 'type' => 'oauth2', // The type of the security scheme. Valid values are "basic", "apiKey" or "oauth2". + 'description' => 'A short description for oauth2 security scheme.', + 'flow' => 'implicit', // The flow used by the OAuth2 security scheme. Valid values are "implicit", "password", "application" or "accessCode". + 'authorizationUrl' => 'http://example.com/auth', // The authorization URL to be used for (implicit/accessCode) + //'tokenUrl' => 'http://example.com/auth' // The authorization URL to be used for (password/application/accessCode) + 'scopes' => [ + 'read:projects' => 'read your projects', + 'write:projects' => 'modify projects in your account', + ] + ],*/ + + /* Open API 3.0 support + 'passport' => [ // Unique name of security + 'type' => 'oauth2', // The type of the security scheme. Valid values are "basic", "apiKey" or "oauth2". + 'description' => 'Laravel passport oauth2 security.', + 'in' => 'header', + 'scheme' => 'https', + 'flows' => [ + "password" => [ + "authorizationUrl" => config('app.url') . '/oauth/authorize', + "tokenUrl" => config('app.url') . '/oauth/token', + "refreshUrl" => config('app.url') . '/token/refresh', + "scopes" => [] + ], + ], + ], + */ + ], + + /* + |-------------------------------------------------------------------------- + | Turn this off to remove swagger generation on production + |-------------------------------------------------------------------------- + */ + 'generate_always' => env('SWAGGER_GENERATE_ALWAYS', false), + + /* + |-------------------------------------------------------------------------- + | Edit to set the swagger version number + |-------------------------------------------------------------------------- + */ + 'swagger_version' => env('SWAGGER_VERSION', '3.0'), + + /* + |-------------------------------------------------------------------------- + | Edit to trust the proxy's ip address - needed for AWS Load Balancer + |-------------------------------------------------------------------------- + */ + 'proxy' => false, + + /* + |-------------------------------------------------------------------------- + | Configs plugin allows to fetch external configs instead of passing them to SwaggerUIBundle. + | See more at: https://github.com/swagger-api/swagger-ui#configs-plugin + |-------------------------------------------------------------------------- + */ + + 'additional_config_url' => null, + + /* + |-------------------------------------------------------------------------- + | Apply a sort to the operation list of each API. It can be 'alpha' (sort by paths alphanumerically), + | 'method' (sort by HTTP method). + | Default is the order returned by the server unchanged. + |-------------------------------------------------------------------------- + */ + + 'operations_sort' => env('L5_SWAGGER_OPERATIONS_SORT', null), + + /* + |-------------------------------------------------------------------------- + | Uncomment to pass the validatorUrl parameter to SwaggerUi init on the JS + | side. A null value here disables validation. + |-------------------------------------------------------------------------- + */ + + 'validator_url' => null, + + /* + |-------------------------------------------------------------------------- + | Uncomment to add constants which can be used in anotations + |-------------------------------------------------------------------------- + */ + 'constants' => [ + // 'SWAGGER_LUME_CONST_HOST' => env('SWAGGER_LUME_CONST_HOST', 'http://my-default-host.com'), + 'API_DESCRIPTION' => <<unique(['request_hash' => 1], 'request_hash'); + }); + + $mapped[] = $table; + } + + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + + } +} diff --git a/database/migrations/2020_05_21_214747_create_queue_index.php b/database/migrations/2020_05_21_214747_create_queue_index.php new file mode 100644 index 0000000..9bd0d6c --- /dev/null +++ b/database/migrations/2020_05_21_214747_create_queue_index.php @@ -0,0 +1,37 @@ +index(['queue', 'reserved_at']); + $table->bigIncrements('id'); + $table->string('queue'); + $table->longText('payload'); + $table->tinyInteger('attempts')->unsigned(); + $table->unsignedInteger('reserved_at')->nullable(); + $table->unsignedInteger('available_at'); + $table->unsignedInteger('created_at'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists(env('QUEUE_TABLE', 'jobs')); + } +} diff --git a/database/migrations/2020_05_21_215040_create_queue_failed_index.php b/database/migrations/2020_05_21_215040_create_queue_failed_index.php new file mode 100644 index 0000000..f684aca --- /dev/null +++ b/database/migrations/2020_05_21_215040_create_queue_failed_index.php @@ -0,0 +1,35 @@ +increments('id'); + $table->text('connection'); + $table->text('queue'); + $table->longText('payload'); + $table->longText('exception'); + $table->timestamp('failed_at')->useCurrent(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists(env('QUEUE_FAILED_TABLE', 'jobs_failed')); + } +} diff --git a/database/migrations/2020_06_07_230022_create_anime_table.php b/database/migrations/2020_06_07_230022_create_anime_table.php new file mode 100644 index 0000000..d31c643 --- /dev/null +++ b/database/migrations/2020_06_07_230022_create_anime_table.php @@ -0,0 +1,78 @@ +unique(['request_hash' => 1], 'request_hash'); + $table->unique(['mal_id' => 1], 'mal_id'); + $table->string('url'); + $table->string('images'); + $table->string('trailer_url'); + $table->index('title'); + $table->index('title_english'); + $table->index('title_japanese'); + $table->enum('type', ['TV', 'Movie', 'OVA', 'Special', 'ONA', 'Music'])->index('type'); + $table->index('source'); + $table->integer('episodes')->index('episodes'); + $table->string('status')->index(); + $table->boolean('airing'); + $table->string( 'duration'); + $table->string('rating')->index('rating'); + $table->float('score')->index('score'); + $table->integer('scored_by')->index('scored_by'); + $table->integer('rank')->index('rank')->nullable(); + $table->integer('popularity')->index('popularity'); + $table->integer('members')->index('members'); + $table->integer('favorites')->index('favorites'); + $table->string('synopsis')->nullable(); + $table->string('background')->nullable(); + $table->index('genres.mal_id'); + $table->index('licensors.mal_id'); + $table->index('producers.mal_id'); + $table->index('studios.mal_id'); + $table->index(['aired.from' => 1], 'start_date'); + $table->index(['aired.to' => 1], 'end_date'); + $table->index([ + 'title' => 'text', + 'title_japanese' => 'text', + 'title_english' => 'text', + 'title_synonyms' => 'text', + ], + 'anime_search_index', + null, + [ + 'weights' => [ + 'title' => 50, + 'title_japanese' => 10, + 'title_english' => 10, + 'title_synonyms' => 1 + ], + 'name' => 'anime_search_index' + ] + ); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('anime'); + } +} diff --git a/database/migrations/2020_06_08_092051_create_manga_table.php b/database/migrations/2020_06_08_092051_create_manga_table.php new file mode 100644 index 0000000..7c9d2f6 --- /dev/null +++ b/database/migrations/2020_06_08_092051_create_manga_table.php @@ -0,0 +1,71 @@ +unique(['request_hash' => 1], 'request_hash'); + $table->unique(['mal_id' => 1], 'mal_id'); + $table->string('url'); + $table->string('images'); + $table->index('title'); + $table->index('title_english'); + $table->index('title_japanese'); + $table->enum('type', ['Manga', 'Novel', 'Light Novel', 'One-shot', 'Doujinshi', 'Manhwa', 'Manhua', 'OEL']); + $table->integer('chapters')->index('chapters'); + $table->integer('volumes')->index('volumes'); + $table->string('status')->index(); + $table->boolean('publishing'); + $table->float('score')->index('score'); + $table->float('scored_by')->index('scored_by'); + $table->integer('rank')->index('rank'); + $table->integer('popularity')->index('popularity'); + $table->integer('members')->index('members'); + $table->integer('favorites')->index('favorites'); + $table->string('synopsis')->nullable(); + $table->string('background')->nullable(); + $table->index('genres.mal_id'); + $table->index('serializations.mal_id'); + $table->index(['published.from' => 1], 'start_date'); + $table->index(['published.to' => 1], 'end_date'); + $table->index([ + 'title' => 'text', + 'title_japanese' => 'text', + 'title_english' => 'text', + 'title_synonyms' => 'text', + ], + 'manga_search_index', + null, + [ + 'weights' => [ + 'title' => 50, + 'title_japanese' => 10, + 'title_english' => 10, + 'title_synonyms' => 1 + ], + 'name' => 'manga_search_index' + ] + ); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('manga'); + } +} diff --git a/database/migrations/2020_06_08_093231_create_people_table.php b/database/migrations/2020_06_08_093231_create_people_table.php new file mode 100644 index 0000000..a88acf1 --- /dev/null +++ b/database/migrations/2020_06_08_093231_create_people_table.php @@ -0,0 +1,62 @@ +unique(['request_hash' => 1], 'request_hash'); + $table->unique(['mal_id' => 1], 'mal_id'); + $table->string('url'); + $table->string('images'); + $table->string('website_url'); + $table->index('name'); + $table->string('given_name')->index()->nullable(); + $table->string('family_name')->index()->nullable(); + $table->index('alternate_names'); + $table->date('birthday')->index(); + $table->integer('member_favorites')->index('member_favorites'); + $table->string('about')->nullable(); + $table->index('voice_acting_roles'); + $table->index('anime_staff_positions'); + $table->index('published_manga'); + $table->index([ + 'name' => 'text', + 'given_name' => 'text', + 'family_name' => 'text', + 'alternate_names' => 'text', + ], + 'people_search_index', + null, + [ + 'weights' => [ + 'name' => 50, + 'given_name' => 10, + 'family_name' => 10, + 'alternate_names' => 1 + ], + 'name' => 'people_search_index' + ] + ); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('people'); + } +} diff --git a/database/migrations/2020_06_08_093529_create_characters_table.php b/database/migrations/2020_06_08_093529_create_characters_table.php new file mode 100644 index 0000000..6fa7dec --- /dev/null +++ b/database/migrations/2020_06_08_093529_create_characters_table.php @@ -0,0 +1,52 @@ +unique(['request_hash' => 1], 'request_hash'); + $table->unique(['mal_id' => 1], 'mal_id'); + $table->string('url'); + $table->string('images'); + $table->index('name'); + $table->index('name_kanji'); + $table->index('nicknames'); + $table->integer('member_favorites')->index('member_favorites'); + $table->string('about')->nullable(); + $table->index([ + 'name' => 'text', + 'nicknames' => 'text', + ], + 'characters_search_index', + null, + [ + 'weights' => [ + 'name' => 50, + 'nicknames' => 10, + ], + 'name' => 'characters_search_index' + ] + ); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('characters'); + } +} diff --git a/database/migrations/2020_07_10_182531_create_magazines_table.php b/database/migrations/2020_07_10_182531_create_magazines_table.php new file mode 100644 index 0000000..8c10daf --- /dev/null +++ b/database/migrations/2020_07_10_182531_create_magazines_table.php @@ -0,0 +1,33 @@ +unique(['mal_id' => 1], 'mal_id'); + $table->index('count'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('magazines'); + } +} diff --git a/database/migrations/2020_07_11_120833_create_clubs_table.php b/database/migrations/2020_07_11_120833_create_clubs_table.php new file mode 100644 index 0000000..6567048 --- /dev/null +++ b/database/migrations/2020_07_11_120833_create_clubs_table.php @@ -0,0 +1,37 @@ +unique(['request_hash' => 1], 'request_hash'); + $table->unique(['mal_id' => 1], 'mal_id'); + $table->index('title'); + $table->index('members_count'); + $table->index('pictures_count'); + $table->index('category'); + $table->date('created'); + $table->index('type'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('clubs'); + } +} diff --git a/database/migrations/2020_07_11_121125_create_producers_table.php b/database/migrations/2020_07_11_121125_create_producers_table.php new file mode 100644 index 0000000..2fc3787 --- /dev/null +++ b/database/migrations/2020_07_11_121125_create_producers_table.php @@ -0,0 +1,32 @@ +unique(['mal_id' => 1], 'mal_id'); + $table->index('count'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('producers'); + } +} diff --git a/database/migrations/2020_07_11_124330_create_profiles_table.php b/database/migrations/2020_07_11_124330_create_profiles_table.php new file mode 100644 index 0000000..dde22cc --- /dev/null +++ b/database/migrations/2020_07_11_124330_create_profiles_table.php @@ -0,0 +1,38 @@ +string('request_hash'); + $table->unique(['mal_id' => 1], 'mal_id'); + $table->unique(['username' => 1], 'username'); + $table->date('last_online')->index(); + $table->index('gender'); + $table->date('birthday')->index(); + $table->index('location'); + $table->date('joined')->index(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('users'); + } +} diff --git a/database/migrations/2021_01_16_105532_create_genres_anime_table.php b/database/migrations/2021_01_16_105532_create_genres_anime_table.php new file mode 100644 index 0000000..2cf9143 --- /dev/null +++ b/database/migrations/2021_01_16_105532_create_genres_anime_table.php @@ -0,0 +1,45 @@ +unique(['mal_id' => 1], 'mal_id'); + }); + + Schema::create('demographics_anime', function (Blueprint $table) { + $table->unique(['mal_id' => 1], 'mal_id'); + }); + + Schema::create('explicit_genres_anime', function (Blueprint $table) { + $table->unique(['mal_id' => 1], 'mal_id'); + }); + + Schema::create('themes_anime', function (Blueprint $table) { + $table->unique(['mal_id' => 1], 'mal_id'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('genres_anime'); + Schema::dropIfExists('demographics_anime'); + Schema::dropIfExists('explicit_genres_anime'); + Schema::dropIfExists('themes_anime'); + } +} diff --git a/database/migrations/2021_01_16_105536_create_genres_manga_table.php b/database/migrations/2021_01_16_105536_create_genres_manga_table.php new file mode 100644 index 0000000..ae046ed --- /dev/null +++ b/database/migrations/2021_01_16_105536_create_genres_manga_table.php @@ -0,0 +1,45 @@ +unique(['mal_id' => 1], 'mal_id'); + }); + + Schema::create('explicit_genres_manga', function (Blueprint $table) { + $table->unique(['mal_id' => 1], 'mal_id'); + }); + + Schema::create('demographics_manga', function (Blueprint $table) { + $table->unique(['mal_id' => 1], 'mal_id'); + }); + + Schema::create('themes_manga', function (Blueprint $table) { + $table->unique(['mal_id' => 1], 'mal_id'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('genres_manga'); + Schema::dropIfExists('explicit_genres_manga'); + Schema::dropIfExists('demographics_manga'); + Schema::dropIfExists('themes_manga'); + } +} diff --git a/public/index.php b/public/index.php index 1cf3ae2..7da0be5 100755 --- a/public/index.php +++ b/public/index.php @@ -29,7 +29,7 @@ ob_start("ob_gzhandler"); if (!env('APP_DEBUG')) { header("Content-Type: application/json"); header("Access-Control-Allow-Origin: *"); - header("Access-Control-Allow-Methods: *"); + header("Access-Control-Allow-Methods: GET"); } $app->run(); diff --git a/resources/views/vendor/swagger-lume/index.blade.php b/resources/views/vendor/swagger-lume/index.blade.php new file mode 100644 index 0000000..a984948 --- /dev/null +++ b/resources/views/vendor/swagger-lume/index.blade.php @@ -0,0 +1,101 @@ + + + + + + {{config('swagger-lume.api.title')}} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + diff --git a/routes/web.v3.php b/routes/web.v3.php deleted file mode 100755 index d7abfc9..0000000 --- a/routes/web.v3.php +++ /dev/null @@ -1,304 +0,0 @@ -get('/', function () use ($router) { - return response()->json([ - 'Author' => '@irfanDahir', - 'Discord' => 'http://discord.jikan.moe', - 'Version' => JIKAN_REST_API_VERSION, - 'JikanPHP' => JIKAN_PARSER_VERSION, - 'Website' => 'https://jikan.moe', - 'Docs' => 'https://jikan.docs.apiary.io', - 'GitHub' => 'https://github.com/jikan-me/jikan', - 'PRODUCTION_API_URL' => 'https://api.jikan.moe/v3/', - 'STATUS_URL' => 'https://status.jikan.moe' - ]); -}); - -$router->group( - [ - 'prefix' => 'meta' - ], - function () use ($router) { - $router->get('/status', [ - 'uses' => 'MetaController@status' - ]); - - $router->group( - [ - 'prefix' => 'requests' - ], - function () use ($router) { - $router->get('/{type:[a-z]+}/{period:[a-z]+}[/{offset:[0-9]+}]', [ - 'uses' => 'MetaController@requests' - ]); - } - ); - } -); - -$router->group( - [ - 'prefix' => 'anime/{id:[0-9]+}' - ], - function () use ($router) { - $router->get('/', [ - 'uses' => 'AnimeController@main' - ]); - - $router->get('/characters_staff', [ - 'uses' => 'AnimeController@characters_staff' - ]); - - $router->get('/episodes[/{page:[0-9]+}]', [ - 'uses' => 'AnimeController@episodes' - ]); - - $router->get('/news', [ - 'uses' => 'AnimeController@news' - ]); - - $router->get('/forum[/{topic:[A-Za-z]+}]', [ - 'uses' => 'AnimeController@forum' - ]); - - $router->get('/videos', [ - 'uses' => 'AnimeController@videos' - ]); - - $router->get('/pictures', [ - 'uses' => 'AnimeController@pictures' - ]); - - $router->get('/stats', [ - 'uses' => 'AnimeController@stats' - ]); - - $router->get('/moreinfo', [ - 'uses' => 'AnimeController@moreInfo' - ]); - - $router->get('/recommendations', [ - 'uses' => 'AnimeController@recommendations' - ]); - - $router->get('/userupdates[/{page:[0-9]+}]', [ - 'uses' => 'AnimeController@userupdates' - ]); - - $router->get('/reviews[/{page:[0-9]+}]', [ - 'uses' => 'AnimeController@reviews' - ]); - } -); - -$router->group( - [ - 'prefix' => 'manga/{id:[0-9]+}' - ], - function () use ($router) { - $router->get('/', [ - 'uses' => 'MangaController@main' - ]); - - $router->get('/characters', [ - 'uses' => 'MangaController@characters' - ]); - - $router->get('/news', [ - 'uses' => 'MangaController@news' - ]); - - $router->get('/forum[/{topic:[A-Za-z]+}]', [ - 'uses' => 'MangaController@forum' - ]); - - $router->get('/pictures', [ - 'uses' => 'MangaController@pictures' - ]); - - $router->get('/stats', [ - 'uses' => 'MangaController@stats' - ]); - - $router->get('/moreinfo', [ - 'uses' => 'MangaController@moreInfo' - ]); - - $router->get('/recommendations', [ - 'uses' => 'MangaController@recommendations' - ]); - - $router->get('/userupdates[/{page:[0-9]+}]', [ - 'uses' => 'MangaController@userupdates' - ]); - - $router->get('/reviews[/{page:[0-9]+}]', [ - 'uses' => 'MangaController@reviews' - ]); - } -); - -$router->group( - [ - 'prefix' => 'character/{id:[0-9]+}' - ], - function () use ($router) { - $router->get('/', [ - 'uses' => 'CharacterController@main' - ]); - - $router->get('/pictures', [ - 'uses' => 'CharacterController@pictures' - ]); - } -); - -$router->group( - [ - 'prefix' => 'person/{id:[0-9]+}' - ], - function () use ($router) { - $router->get('/', [ - 'uses' => 'PersonController@main' - ]); - - $router->get('/pictures', [ - 'uses' => 'PersonController@pictures' - ]); - } -); - -$router->get('season/archive', [ - 'uses' => 'SeasonController@archive' -]); - -$router->get('season/later', [ - 'uses' => 'SeasonController@later' -]); - -$router->get('season[/{year:[0-9]{4}}/{season:[A-Za-z]+}]', [ - 'uses' => 'SeasonController@main' -]); - -$router->get('schedule[/{day:[A-Za-z]+}]', [ - 'uses' => 'ScheduleController@main' -]); - -$router->get('producer/{id:[0-9]+}[/{page:[0-9]+}]', [ - 'uses' => 'ProducerController@main' -]); - -$router->get('magazine/{id:[0-9]+}[/{page:[0-9]+}]', [ - 'uses' => 'MagazineController@main' -]); - -$router->group( - [ - 'prefix' => 'user/{username:[\w\-]+}' - ], - function () use ($router) { - $router->get('/', [ - 'uses' => 'UserController@profile' - ]); - - $router->get('/profile', [ - 'uses' => 'UserController@profile' - ]); - - $router->get('/history[/{type:[A-Za-z]+}]', [ - 'uses' => 'UserController@history' - ]); - - $router->get('/friends[/{page:[0-9]+}]', [ - 'uses' => 'UserController@friends' - ]); - - $router->get('/animelist[/{status:[A-Za-z]+}[/{page:[0-9]+}]]', [ - 'uses' => 'UserController@animelist' - ]); - - $router->get('/mangalist[/{status:[A-Za-z]+}[/{page:[0-9]+}]]', [ - 'uses' => 'UserController@mangalist' - ]); - } -); - -$router->group( - [ - 'prefix' => 'genre' - ], - function () use ($router) { - $router->get('/anime/{id:[0-9]+}[/{page:[0-9]+}]', [ - 'uses' => 'GenreController@anime' - ]); - - $router->get('/manga/{id:[0-9]+}[/{page:[0-9]+}]', [ - 'uses' => 'GenreController@manga' - ]); - } -); - -$router->group( - [ - 'prefix' => 'top' - ], - function () use ($router) { - $router->get('/anime[/{page:[0-9]+}[/{type:[A-Za-z]+}]]', [ - 'uses' => 'TopController@anime' - ]); - - $router->get('/manga[/{page:[0-9]+}[/{type:[A-Za-z]+}]]', [ - 'uses' => 'TopController@manga' - ]); - - $router->get('/characters[/{page:[0-9]+}]', [ - 'uses' => 'TopController@characters' - ]); - - $router->get('/people[/{page:[0-9]+}]', [ - 'uses' => 'TopController@people' - ]); - } -); - -$router->group( - [ - 'prefix' => 'search' - ], - function () use ($router) { - $router->get('/anime[/{page:[0-9]+}]', [ - 'uses' => 'SearchController@anime' - ]); - - $router->get('/manga[/{page:[0-9]+}]', [ - 'uses' => 'SearchController@manga' - ]); - - $router->get('/character[/{page:[0-9]+}]', [ - 'uses' => 'SearchController@character' - ]); - - $router->get('/person[/{page:[0-9]+}]', [ - 'uses' => 'SearchController@people' - ]); - - $router->get('/people[/{page:[0-9]+}]', [ - 'uses' => 'SearchController@people' - ]); - } -); - -$router->group( - [ - 'prefix' => 'club/{id:[0-9]+}' - ], - function () use ($router) { - $router->get('/', [ - 'uses' => 'ClubController@main' - ]); - - $router->get('/members[/{page:[0-9]+}]', [ - 'uses' => 'ClubController@members' - ]); - } -); diff --git a/routes/web.v4.php b/routes/web.v4.php new file mode 100755 index 0000000..e4375f5 --- /dev/null +++ b/routes/web.v4.php @@ -0,0 +1,503 @@ +get('/', function () use ($router) { + return response()->json([ + 'author_url' => 'https://github.com/irfan-dahir', + 'discord_url' => 'http://discord.jikan.moe', + 'version' => env('APP_VERSION'), + 'parser_version' => JIKAN_PARSER_VERSION, + 'website_url' => 'https://jikan.moe', + 'documentation_url' => 'https://docs.api.jikan.moe/', + 'github_url' => 'https://github.com/jikan-me/jikan-rest', + 'parser_github_url' => 'https://github.com/jikan-me/jikan', + 'production_api_url' => 'https://api.jikan.moe/v4/', + 'status_url' => 'https://status.jikan.moe', + 'myanimelist_heartbeat' => [ + 'status' => \App\Providers\SourceHeartbeatProvider::getHeartbeatStatus(), + 'score' => \App\Providers\SourceHeartbeatProvider::getHeartbeatScore(), + 'down' => \App\Providers\SourceHeartbeatProvider::isFailoverEnabled(), + 'last_downtime' => \App\Providers\SourceHeartbeatProvider::getLastDowntime() + ] + ]); +}); + +$router->get('/anime', [ + 'uses' => 'SearchController@anime' +]); + +$router->group( + [ + 'prefix' => 'anime/{id:[0-9]+}' + ], + function () use ($router) { + $router->get('/', [ + 'uses' => 'AnimeController@main' + ]); + + $router->get('/characters', [ + 'uses' => 'AnimeController@characters' + ]); + + $router->get('/staff', [ + 'uses' => 'AnimeController@staff' + ]); + + $router->get('/episodes', [ + 'uses' => 'AnimeController@episodes' + ]); + + $router->get('/episodes/{episodeId:[0-9]+}', [ + 'uses' => 'AnimeController@episode' + ]); + + $router->get('/news', [ + 'uses' => 'AnimeController@news' + ]); + + $router->get('/forum', [ + 'uses' => 'AnimeController@forum' + ]); + + $router->get('/videos', [ + 'uses' => 'AnimeController@videos' + ]); + + $router->get('/pictures', [ + 'uses' => 'AnimeController@pictures' + ]); + + $router->get('/statistics', [ + 'uses' => 'AnimeController@stats' + ]); + + $router->get('/moreinfo', [ + 'uses' => 'AnimeController@moreInfo' + ]); + + $router->get('/recommendations', [ + 'uses' => 'AnimeController@recommendations' + ]); + + $router->get('/userupdates', [ + 'uses' => 'AnimeController@userupdates' + ]); + + $router->get('/reviews', [ + 'uses' => 'AnimeController@reviews' + ]); + + $router->get('/relations', [ + 'uses' => 'AnimeController@relations' + ]); + + $router->get('/themes', [ + 'uses' => 'AnimeController@themes' + ]); + } +); + +$router->get('/manga', [ + 'uses' => 'SearchController@manga' +]); + +$router->group( + [ + 'prefix' => 'manga/{id:[0-9]+}' + ], + function () use ($router) { + $router->get('/', [ + 'uses' => 'MangaController@main' + ]); + + $router->get('/characters', [ + 'uses' => 'MangaController@characters' + ]); + + $router->get('/news', [ + 'uses' => 'MangaController@news' + ]); + + $router->get('/forum', [ + 'uses' => 'MangaController@forum' + ]); + + $router->get('/pictures', [ + 'uses' => 'MangaController@pictures' + ]); + + $router->get('/statistics', [ + 'uses' => 'MangaController@stats' + ]); + + $router->get('/moreinfo', [ + 'uses' => 'MangaController@moreInfo' + ]); + + $router->get('/recommendations', [ + 'uses' => 'MangaController@recommendations' + ]); + + $router->get('/userupdates', [ + 'uses' => 'MangaController@userupdates' + ]); + + $router->get('/reviews', [ + 'uses' => 'MangaController@reviews' + ]); + + $router->get('/relations', [ + 'uses' => 'MangaController@relations' + ]); + } +); + +$router->get('/characters', [ + 'uses' => 'SearchController@character' +]); + +$router->group( + [ + 'prefix' => 'characters/{id:[0-9]+}' + ], + function () use ($router) { + $router->get('/', [ + 'uses' => 'CharacterController@main' + ]); + + $router->get('/anime', [ + 'uses' => 'CharacterController@anime' + ]); + + $router->get('/voices', [ + 'uses' => 'CharacterController@voices' + ]); + + $router->get('/manga', [ + 'uses' => 'CharacterController@manga' + ]); + + $router->get('/pictures', [ + 'uses' => 'CharacterController@pictures' + ]); + } +); + +$router->get('/people', [ + 'uses' => 'SearchController@people' +]); +$router->group( + [ + 'prefix' => 'people/{id:[0-9]+}' + ], + function () use ($router) { + $router->get('/', [ + 'uses' => 'PersonController@main' + ]); + + $router->get('/anime', [ + 'uses' => 'PersonController@anime' + ]); + + $router->get('/voices', [ + 'uses' => 'PersonController@voices' + ]); + + $router->get('/manga', [ + 'uses' => 'PersonController@manga' + ]); + + $router->get('/pictures', [ + 'uses' => 'PersonController@pictures' + ]); + } +); + + +$router->group( + [ + 'prefix' => 'seasons' + ], + function () use ($router) { + $router->get('/', [ + 'uses' => 'SeasonController@archive' + ]); + + $router->get('/now', [ + 'uses' => 'SeasonController@main' + ]); + + $router->get('/upcoming', [ + 'uses' => 'SeasonController@later' + ]); + + $router->get('/{year:[0-9]{4}}/{season:[A-Za-z]+}', [ + 'uses' => 'SeasonController@main' + ]); + } +); + +$router->get('schedules[/{day:[A-Za-z]+}]', [ + 'uses' => 'ScheduleController@main' +]); + +$router->group( + [ + 'prefix' => 'producers' + ], + function() use ($router) { + $router->get('/', [ + 'uses' => 'ProducerController@main', + ]); + } +); + +$router->group( + [ + 'prefix' => 'magazines' + ], + function() use ($router) { + $router->get('/', [ + 'uses' => 'MagazineController@main', + ]); + } +); + + +$router->group( + [ + 'prefix' => 'users' + ], + function () use ($router) { + $router->get('/', [ + 'uses' => 'SearchController@users' + ]); + + $router->get('/recentlyonline', [ + 'uses' => 'UserController@recentlyOnline' + ]); + + $router->get('/userbyid/{id:[0-9]+}', [ + 'uses' => 'SearchController@userById' + ]); + + $router->group( + [ + 'prefix' => '/{username:[\w\-]+}' + ], + function () use ($router) { + $router->get('/', [ + 'uses' => 'UserController@profile' + ]); + + $router->get('/statistics', [ + 'uses' => 'UserController@statistics' + ]); + + $router->get('/favorites', [ + 'uses' => 'UserController@favorites' + ]); + + $router->get('/userupdates', [ + 'uses' => 'UserController@userupdates' + ]); + + $router->get('/about', [ + 'uses' => 'UserController@about' + ]); + + $router->get('/history[/{type:[A-Za-z]+}]', [ + 'uses' => 'UserController@history' + ]); + + $router->get('/friends[/{page:[0-9]+}]', [ + 'uses' => 'UserController@friends' + ]); + + $router->get('/animelist[/{status:[A-Za-z]+}]', [ + 'uses' => 'UserController@animelist' + ]); + + $router->get('/mangalist[/{status:[A-Za-z]+}]', [ + 'uses' => 'UserController@mangalist' + ]); + + $router->get('/recommendations', [ + 'uses' => 'UserController@recommendations' + ]); + + $router->get('/reviews', [ + 'uses' => 'UserController@reviews' + ]); + + $router->get('/clubs', [ + 'uses' => 'UserController@clubs' + ]); + } + ); + } +); + +$router->group( + [ + 'prefix' => 'genres' + ], + function () use ($router) { + + $router->get('/anime', [ + 'uses' => 'GenreController@anime' + ]); + + $router->get('/manga', [ + 'uses' => 'GenreController@manga' + ]); + } +); + +$router->group( + [ + 'prefix' => 'top' + ], + function () use ($router) { + $router->get('/anime', [ + 'uses' => 'TopController@anime' + ]); + + $router->get('/manga', [ + 'uses' => 'TopController@manga' + ]); + + $router->get('/characters', [ + 'uses' => 'TopController@characters' + ]); + + $router->get('/people', [ + 'uses' => 'TopController@people' + ]); + + $router->get('/reviews', [ + 'uses' => 'TopController@reviews' + ]); + } +); + +$router->group( + [ + 'prefix' => 'clubs' + ], + function () use ($router) { + + $router->get('/{id:[0-9]+}', [ + 'uses' => 'ClubController@main' + ]); + + $router->get('/{id:[0-9]+}/members', [ + 'uses' => 'ClubController@members' + ]); + + $router->get('/{id:[0-9]+}/staff', [ + 'uses' => 'ClubController@staff' + ]); + + $router->get('/{id:[0-9]+}/relations', [ + 'uses' => 'ClubController@relations' + ]); + } +); + +$router->group( + [ + 'prefix' => 'reviews' + ], + function () use ($router) { + + $router->get('/anime', [ + 'uses' => 'ReviewsController@anime' + ]); + + $router->get('/manga', [ + 'uses' => 'ReviewsController@manga' + ]); + } +); + +$router->group( + [ + 'prefix' => 'recommendations' + ], + function () use ($router) { + $router->get('/anime', [ + 'uses' => 'RecommendationsController@anime' + ]); + + $router->get('/manga', [ + 'uses' => 'RecommendationsController@manga' + ]); + } +); + +$router->group( + [ + 'prefix' => 'watch' + ], + function () use ($router) { + $router->get('/episodes', [ + 'uses' => 'WatchController@recentEpisodes' + ]); + + $router->get('/episodes/popular', [ + 'uses' => 'WatchController@popularEpisodes' + ]); + + $router->get('/promos', [ + 'uses' => 'WatchController@recentPromos' + ]); + + $router->get('/promos/popular', [ + 'uses' => 'WatchController@popularPromos' + ]); + } +); + +$router->group( + [ + 'prefix' => 'random' + ], + function() use ($router) { + $router->get('/anime', [ + 'uses' => 'RandomController@anime', + ]); + + $router->get('/manga', [ + 'uses' => 'RandomController@manga', + ]); + + $router->get('/characters', [ + 'uses' => 'RandomController@characters', + ]); + + $router->get('/people', [ + 'uses' => 'RandomController@people', + ]); + + $router->get('/users', [ + 'uses' => 'RandomController@users', + ]); + } +); + + +$router->group( + [ + 'prefix' => 'insights' + ], + function() use ($router) { + $router->get('/', [ + 'uses' => 'InsightsController@main' + ]); + + $router->get('/trends', [ + 'uses' => 'InsightsController@trends' + ]); + } +); \ No newline at end of file diff --git a/storage/api-docs/api-docs.json b/storage/api-docs/api-docs.json new file mode 100644 index 0000000..ec5562a --- /dev/null +++ b/storage/api-docs/api-docs.json @@ -0,0 +1,6706 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "Jikan API", + "description": "[Jikan](https://jikan.moe) is an **Unofficial** MyAnimeList API.\r\nIt scrapes the website to satisfy the need for a complete API - which MyAnimeList lacks.\r\n\r\n# Information\r\n\r\n⚡ Jikan is powered by it's awesome backers - 🙏 [Become a backer](https://www.patreon.com/jikan)\r\n\r\n## Rate Limiting\r\n\r\n| Duration | Requests |\r\n|----|----|\r\n| Daily | **Unlimited** |\r\n| Per Minute | 60 requests |\r\n| Per Second | 3 requests |\r\n\r\n\r\n## JSON Notes\r\n- Any property (except arrays or objects) whose value does not exist or is undetermined, will be `null`.\r\n- Any array or object property whose value does not exist or is undetermined, will be `null`.\r\n- Any `score` property whose value does not exist or is undetermined, will be `0`.\r\n- All dates and timestamps are returned in [ISO8601](https://en.wikipedia.org/wiki/ISO_8601) format and in UTC timezone\r\n\r\n## Caching\r\nBy **CACHING**, we refer to the data parsed from MyAnimeList which is stored temporarily on our servers to provide better API performance.\r\n\r\nAll requests, by default are cached for **24 hours** except the following endpoints which have their own unique cache **Time To Live**.\r\n\r\n| Request | TTL |\r\n| ---- | ---- |\r\n| All (Default) | 24 hours |\r\n| User Anime/Manga List | 5 minutes |\r\n\r\n\r\nThe following response headers will detail cache information.\r\n\r\n| Header | Remarks |\r\n| ---- | ---- |\r\n| `Expires` | Expiry unix timestamp |\r\n\r\n\r\n## Allowed HTTP(s) requests\r\n\r\n**Jikan REST API does not provide authenticated requests for MyAnimeList.** This means you can not use it to update your anime/manga list.\r\nOnly GET requests are supported which return READ-ONLY data.\r\n\r\n## HTTP Responses\r\n\r\n| HTTP Status | Remarks |\r\n| ---- | ---- |\r\n| `200 - OK` | The request was successful |\r\n| `304 - Not Modified` | You have the latest data (Cache Validation response) |\r\n| `400 - Bad Request` | You've made an invalid request. Recheck documentation |\r\n| `404 - Not Found` | The resource was not found or MyAnimeList responded with a `404` |\r\n| `405 - Method Not Allowed` | Requested Method is not supported for resource. Only `GET` requests are allowed |\r\n| `429 - Too Many Request` | You are being rate limited by Jikan or MyAnimeList is rate-limiting our servers (specified in the error response) |\r\n| `500 - Internal Server Error` | Something is not working on our end. If you see an error response with a `report_url` URL, please click on it to open an auto-generated GitHub issue |\r\n| `503 - Service Unavailable` | The service has broke. |\r\n\r\n\r\n## JSON Error Response\r\n\r\n```json\r\n {\r\n \"status\": 404,\r\n \"type\": \"BadResponseException\",\r\n \"message\": \"Resource does not exist\",\r\n \"error\": \"Something Happened\",\r\n \"report_url\": \"https://github.com...\"\r\n }\r\n```\r\n\r\n| Property | Remarks |\r\n| ---- | ---- |\r\n| `status` | Returned HTTP Status Code |\r\n| `type` | Thrown Exception |\r\n| `message` | Human-readable error message |\r\n| `error` | Error response and trace from the API |\r\n| `report_url` | Clicking this would redirect you to a generated GitHub issue. ℹ It's only returned on a parser error. |\r\n\r\n\r\n## Cache Validation\r\n\r\n- All requests return a `ETag` header which is an MD5 hash of the response\r\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\r\n- You will get a HTTP `304 - Not Modified` response if the content has not changed\r\n- If the content has changed, you'll get a HTTP `200 - OK` response with the updated JSON response\r\n\r\n![Cache Validation](https://i.imgur.com/925ozVn.png 'Cache Validation')\r\n\r\n## Disclaimer\r\n\r\n- Jikan is not affiliated with MyAnimeList.net.\r\n- Jikan is a free, open-source API. Please use it responsibly.\r\n\r\n----\r\n\r\nBy using the API, you are agreeing to Jikan's [terms of use](https://jikan.moe/terms) policy.\r\n\r\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)", + "url": "http://discord.jikan.moe" + }, + "license": { + "name": "MIT", + "url": "https://github.com/jikan-me/jikan-rest/blob/master/LICENSE" + }, + "version": "4.0.0" + }, + "servers": [ + { + "url": "https://api.jikan.moe/v4", + "description": "Jikan REST API Beta" + } + ], + "paths": { + "/anime/{id}": { + "get": { + "tags": [ + "anime" + ], + "operationId": "getAnimeById", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Returns anime resource", + "content": { + "application/json": { + "schema": { + "properties": { + "data": { + "$ref": "#/components/schemas/anime" + } + }, + "type": "object" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/anime/{id}/characters": { + "get": { + "tags": [ + "anime" + ], + "operationId": "getAnimeCharacters", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Returns anime characters resource", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/anime characters" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/anime/{id}/staff": { + "get": { + "tags": [ + "anime" + ], + "operationId": "getAnimeStaff", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Returns anime staff resource", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/anime staff" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/anime/{id}/episodes": { + "get": { + "tags": [ + "anime" + ], + "operationId": "getAnimeEpisodes", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "$ref": "#/components/parameters/page" + } + ], + "responses": { + "200": { + "description": "Returns a list of anime episodes", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/anime episodes" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/anime/{id}/episodes/{episode}": { + "get": { + "tags": [ + "anime" + ], + "operationId": "getAnimeEpisodeById", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "episode", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Returns a single anime episode resource", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/anime episode" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/anime/{id}/news": { + "get": { + "tags": [ + "anime" + ], + "operationId": "getAnimeNews", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "$ref": "#/components/parameters/page" + } + ], + "responses": { + "200": { + "description": "Returns a list of news articles related to the entry", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/anime news" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/anime/{id}/forum": { + "get": { + "tags": [ + "anime" + ], + "operationId": "getAnimeForum", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "topic", + "in": "query", + "description": "Filter topics", + "required": false, + "schema": { + "type": "string", + "enum": [ + "all", + "episode", + "other" + ] + } + } + ], + "responses": { + "200": { + "description": "Returns a list of forum topics related to the entry", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/forum" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/anime/{id}/videos": { + "get": { + "tags": [ + "anime" + ], + "operationId": "getAnimeVideos", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Returns videos related to the entry", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/anime videos" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/anime/{id}/pictures": { + "get": { + "tags": [ + "anime" + ], + "operationId": "getAnimePictures", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Returns pictures related to the entry", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/pictures variants" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/anime/{id}/statistics": { + "get": { + "tags": [ + "anime" + ], + "operationId": "getAnimeStatistics", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Returns anime statistics", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/anime statistics" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/anime/{id}/moreinfo": { + "get": { + "tags": [ + "anime" + ], + "operationId": "getAnimeMoreInfo", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Returns anime statistics", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/moreinfo" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/anime/{id}/recommendations": { + "get": { + "tags": [ + "anime" + ], + "operationId": "getAnimeRecommendations", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Returns anime recommendations", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/entry recommendations" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/anime/{id}/userupdates": { + "get": { + "tags": [ + "anime" + ], + "operationId": "getAnimeUserUpdates", + "parameters": [ + { + "$ref": "#/components/parameters/page" + }, + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Returns a list of users who have added/updated/removed the entry on their list", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/anime userupdates" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/anime/{id}/reviews": { + "get": { + "tags": [ + "anime" + ], + "operationId": "getAnimeReviews", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "$ref": "#/components/parameters/page" + } + ], + "responses": { + "200": { + "description": "Returns anime reviews", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/anime reviews" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/anime/{id}/relations": { + "get": { + "tags": [ + "anime" + ], + "operationId": "getAnimeRelations", + "responses": { + "200": { + "description": "Returns anime relations", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/relation" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/anime/{id}/themes": { + "get": { + "tags": [ + "anime" + ], + "operationId": "getAnimeThemes", + "responses": { + "200": { + "description": "Returns anime themes", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/anime themes" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/characters/{id}": { + "get": { + "tags": [ + "characters" + ], + "operationId": "getCharacterById", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Returns character resource", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/character" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/characters/{id}/anime": { + "get": { + "tags": [ + "characters" + ], + "operationId": "getCharacterAnime", + "responses": { + "200": { + "description": "Returns anime that character is in", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/character anime" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/characters/{id}/manga": { + "get": { + "tags": [ + "characters" + ], + "operationId": "getCharacterManga", + "responses": { + "200": { + "description": "Returns manga that character is in", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/character manga" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/characters/{id}/voices": { + "get": { + "tags": [ + "characters" + ], + "operationId": "getCharacterVoiceActors", + "responses": { + "200": { + "description": "Returns the character's voice actors", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/character voice actors" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/characters/{id}/pictures": { + "get": { + "tags": [ + "characters" + ], + "operationId": "getCharacterPictures", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Returns pictures related to the entry", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/pictures" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/clubs/{id}": { + "get": { + "tags": [ + "clubs" + ], + "operationId": "getClubsById", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Returns Club Resource", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/club" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/clubs/{id}/members": { + "get": { + "tags": [ + "clubs" + ], + "operationId": "getClubMembers", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "$ref": "#/components/parameters/page" + } + ], + "responses": { + "200": { + "description": "Returns Club Members Resource", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/pagination" + }, + { + "$ref": "#/components/schemas/club member" + } + ] + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/clubs/{id}/staff": { + "get": { + "tags": [ + "clubs" + ], + "operationId": "getClubStaff", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Returns Club Staff", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/club staff" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/clubs/{id}/relations": { + "get": { + "tags": [ + "clubs" + ], + "operationId": "getClubRelations", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Returns Club Relations", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/club relations" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/genres/anime": { + "get": { + "tags": [ + "genres" + ], + "operationId": "getAnimeGenres", + "parameters": [ + { + "$ref": "#/components/parameters/page" + }, + { + "$ref": "#/components/parameters/limit" + }, + { + "name": "filter", + "in": "query", + "schema": { + "$ref": "#/components/schemas/genre query filter" + } + } + ], + "responses": { + "200": { + "description": "Returns entry genres, explicit_genres, themes and demographics", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/genres" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/genres/manga": { + "get": { + "tags": [ + "genres" + ], + "operationId": "getMangaGenres", + "parameters": [ + { + "$ref": "#/components/parameters/page" + }, + { + "$ref": "#/components/parameters/limit" + }, + { + "name": "filter", + "in": "query", + "schema": { + "$ref": "#/components/schemas/genre query filter" + } + } + ], + "responses": { + "200": { + "description": "Returns entry genres, explicit_genres, themes and demographics", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/genres" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/magazines": { + "get": { + "tags": [ + "magazines" + ], + "operationId": "getMagazines", + "responses": { + "200": { + "description": "Returns magazines collection", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/magazines" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/manga/{id}": { + "get": { + "tags": [ + "manga" + ], + "operationId": "getMangaById", + "responses": { + "200": { + "description": "Returns pictures related to the entry", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/manga" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/manga/{id}/characters": { + "get": { + "tags": [ + "manga" + ], + "operationId": "getMangaCharacters", + "responses": { + "200": { + "description": "Returns manga characters resource", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/manga characters" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/manga/{id}/news": { + "get": { + "tags": [ + "manga" + ], + "operationId": "getMangaNews", + "responses": { + "200": { + "description": "Returns a list of manga news topics", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/manga news" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/manga/{id}/forum": { + "get": { + "tags": [ + "manga" + ], + "operationId": "getMangaTopics", + "responses": { + "200": { + "description": "Returns a list of manga forum topics", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/forum" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/manga/{id}/pictures": { + "get": { + "tags": [ + "manga" + ], + "operationId": "getMangaPictures", + "responses": { + "200": { + "description": "Returns a list of manga forum topics", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/pictures" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/manga/{id}/statistics": { + "get": { + "tags": [ + "manga" + ], + "operationId": "getMangaStatistics", + "responses": { + "200": { + "description": "Returns anime statistics", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/manga statistics" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/manga/{id}/moreinfo": { + "get": { + "tags": [ + "manga" + ], + "operationId": "getMangaMoreInfo", + "responses": { + "200": { + "description": "Returns manga moreinfo", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/moreinfo" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/manga/{id}/recommendations": { + "get": { + "tags": [ + "manga" + ], + "operationId": "getMangaRecommendations", + "responses": { + "200": { + "description": "Returns manga recommendations", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/entry recommendations" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/manga/{id}/userupdates": { + "get": { + "tags": [ + "manga" + ], + "operationId": "getMangaUserUpdates", + "responses": { + "200": { + "description": "Returns manga user updates", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/manga userupdates" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/manga/{id}/reviews": { + "get": { + "tags": [ + "manga" + ], + "operationId": "getMangaReviews", + "responses": { + "200": { + "description": "Returns manga reviews", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/manga reviews" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/manga/{id}/relations": { + "get": { + "tags": [ + "manga" + ], + "operationId": "getMangaRelations", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Returns manga relations", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/relation" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/people/{id}": { + "get": { + "tags": [ + "people" + ], + "operationId": "getPersonById", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Returns pictures related to the entry", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/pictures variants" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/people/{id}/anime": { + "get": { + "tags": [ + "people" + ], + "operationId": "getPersonAnime", + "responses": { + "200": { + "description": "Returns person's anime staff positions", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/person anime" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/people/{id}/voices": { + "get": { + "tags": [ + "people" + ], + "operationId": "getPersonVoices", + "responses": { + "200": { + "description": "Returns person's voice acting roles", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/person voice acting roles" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/people/{id}/manga": { + "get": { + "tags": [ + "people" + ], + "operationId": "getPersonManga", + "responses": { + "200": { + "description": "Returns person's published manga works", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/person manga" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/people/{id}/pictures": { + "get": { + "tags": [ + "people" + ], + "operationId": "getPersonPictures", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Returns a list of pictures of the person", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/person pictures" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/producers": { + "get": { + "tags": [ + "producers" + ], + "operationId": "getProducers", + "responses": { + "200": { + "description": "Returns producers collection", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/producers" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/random/anime": { + "get": { + "tags": [ + "random" + ], + "operationId": "getRandomAnime", + "responses": { + "200": { + "description": "Returns a random anime resource", + "content": { + "application/json": { + "schema": { + "properties": { + "data": { + "$ref": "#/components/schemas/anime" + } + }, + "type": "object" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/random/manga": { + "get": { + "tags": [ + "random" + ], + "operationId": "getRandomManga", + "responses": { + "200": { + "description": "Returns a random manga resource", + "content": { + "application/json": { + "schema": { + "properties": { + "data": { + "$ref": "#/components/schemas/manga" + } + }, + "type": "object" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/random/characters": { + "get": { + "tags": [ + "random" + ], + "operationId": "getRandomCharacters", + "responses": { + "200": { + "description": "Returns a random character resource", + "content": { + "application/json": { + "schema": { + "properties": { + "data": { + "$ref": "#/components/schemas/character" + } + }, + "type": "object" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/random/people": { + "get": { + "tags": [ + "random" + ], + "operationId": "getRandomPeople", + "responses": { + "200": { + "description": "Returns a random person resource", + "content": { + "application/json": { + "schema": { + "properties": { + "data": { + "$ref": "#/components/schemas/person" + } + }, + "type": "object" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/random/users": { + "get": { + "tags": [ + "random" + ], + "operationId": "getRandomUsers", + "responses": { + "200": { + "description": "Returns a random user profile resource", + "content": { + "application/json": { + "schema": { + "properties": { + "data": { + "$ref": "#/components/schemas/user profile" + } + }, + "type": "object" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/recommendations/anime": { + "get": { + "tags": [ + "recommendations" + ], + "operationId": "getRecentAnimeRecommendations", + "responses": { + "200": { + "description": "Returns recent anime recommendations", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/recommendations" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/recommendations/manga": { + "get": { + "tags": [ + "recommendations" + ], + "operationId": "getRecentMangaRecommendations", + "responses": { + "200": { + "description": "Returns recent manga recommendations", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/recommendations" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/reviews/anime": { + "get": { + "tags": [ + "reviews" + ], + "operationId": "getRecentAnimeReviews", + "responses": { + "200": { + "description": "Returns recent anime reviews", + "content": { + "application/json": { + "schema": { + "properties": { + "data": { + "allOf": [ + { + "properties": { + "data": { + "type": "array", + "items": { + "allOf": [ + { + "properties": { + "user": { + "$ref": "#/components/schemas/user meta" + } + }, + "type": "object" + }, + { + "properties": { + "anime": { + "$ref": "#/components/schemas/anime meta" + } + }, + "type": "object" + }, + { + "$ref": "#/components/schemas/anime review" + } + ] + } + } + }, + "type": "object" + }, + { + "$ref": "#/components/schemas/pagination" + } + ] + } + }, + "type": "object" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/reviews/manga": { + "get": { + "tags": [ + "reviews" + ], + "operationId": "getRecentMangaReviews", + "responses": { + "200": { + "description": "Returns recent manga reviews", + "content": { + "application/json": { + "schema": { + "properties": { + "data": { + "allOf": [ + { + "properties": { + "data": { + "type": "array", + "items": { + "allOf": [ + { + "properties": { + "user": { + "$ref": "#/components/schemas/user meta" + } + }, + "type": "object" + }, + { + "properties": { + "manga": { + "$ref": "#/components/schemas/manga meta" + } + }, + "type": "object" + }, + { + "$ref": "#/components/schemas/manga review" + } + ] + } + } + }, + "type": "object" + }, + { + "$ref": "#/components/schemas/pagination" + } + ] + } + }, + "type": "object" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/schedules": { + "get": { + "tags": [ + "schedules" + ], + "operationId": "getSchedules", + "parameters": [ + { + "name": "topic", + "in": "path", + "description": "Filter by day", + "required": false, + "schema": { + "type": "string", + "enum": [ + "monday", + "tuesday", + "wednesday", + "thursday", + "friday", + "unknown", + "other" + ] + } + } + ], + "responses": { + "200": { + "description": "Returns weekly schedule", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/schedules" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/anime": { + "get": { + "tags": [ + "anime" + ], + "operationId": "getAnimeSearch", + "parameters": [ + { + "$ref": "#/components/parameters/page" + }, + { + "$ref": "#/components/parameters/limit" + }, + { + "name": "q", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "type", + "in": "query", + "schema": { + "$ref": "#/components/schemas/anime search query type" + } + }, + { + "name": "score", + "in": "query", + "schema": { + "type": "number" + } + }, + { + "name": "status", + "in": "query", + "schema": { + "$ref": "#/components/schemas/anime search query status" + } + }, + { + "name": "rating", + "in": "query", + "schema": { + "$ref": "#/components/schemas/anime search query rating" + } + }, + { + "name": "sfw", + "in": "query", + "description": "Filter out Adult entries", + "schema": { + "type": "boolean" + } + }, + { + "name": "genres", + "in": "query", + "description": "Filter by genre(s) IDs. Can pass multiple with a comma as a delimiter. e.g 1,2,3", + "schema": { + "type": "string" + } + }, + { + "name": "order_by", + "in": "query", + "schema": { + "$ref": "#/components/schemas/anime search query orderby" + } + }, + { + "name": "sort", + "in": "query", + "schema": { + "$ref": "#/components/schemas/search query sort" + } + }, + { + "name": "letter", + "in": "query", + "description": "Return entries starting with the given letter", + "schema": { + "type": "string" + } + }, + { + "name": "producer", + "in": "query", + "description": "Filter by producer(s) IDs. Can pass multiple with a comma as a delimiter. e.g 1,2,3", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Returns search results for anime", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/anime search" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/manga": { + "get": { + "tags": [ + "manga" + ], + "operationId": "getMangaSearch", + "parameters": [ + { + "$ref": "#/components/parameters/page" + }, + { + "$ref": "#/components/parameters/limit" + }, + { + "name": "q", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "type", + "in": "query", + "schema": { + "$ref": "#/components/schemas/manga search query type" + } + }, + { + "name": "score", + "in": "query", + "schema": { + "type": "number" + } + }, + { + "name": "status", + "in": "query", + "schema": { + "$ref": "#/components/schemas/manga search query status" + } + }, + { + "name": "sfw", + "in": "query", + "description": "Filter out Adult entries", + "schema": { + "type": "boolean" + } + }, + { + "name": "genres", + "in": "query", + "description": "Filter by genre(s) IDs. Can pass multiple with a comma as a delimiter. e.g 1,2,3", + "schema": { + "type": "string" + } + }, + { + "name": "order_by", + "in": "query", + "schema": { + "$ref": "#/components/schemas/manga search query orderby" + } + }, + { + "name": "sort", + "in": "query", + "schema": { + "$ref": "#/components/schemas/search query sort" + } + }, + { + "name": "letter", + "in": "query", + "description": "Return entries starting with the given letter", + "schema": { + "type": "string" + } + }, + { + "name": "magazine", + "in": "query", + "description": "Filter by producer(s) IDs. Can pass multiple with a comma as a delimiter. e.g 1,2,3", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Returns search results for manga", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/manga search" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/people": { + "get": { + "tags": [ + "people" + ], + "operationId": "getPeopleSearch", + "parameters": [ + { + "$ref": "#/components/parameters/page" + }, + { + "$ref": "#/components/parameters/limit" + }, + { + "name": "q", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "order_by", + "in": "query", + "schema": { + "$ref": "#/components/schemas/people search query orderby" + } + }, + { + "name": "sort", + "in": "query", + "schema": { + "$ref": "#/components/schemas/search query sort" + } + }, + { + "name": "letter", + "in": "query", + "description": "Return entries starting with the given letter", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Returns search results for people", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/people search" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/characters": { + "get": { + "tags": [ + "characters" + ], + "operationId": "getCharactersSearch", + "parameters": [ + { + "$ref": "#/components/parameters/page" + }, + { + "$ref": "#/components/parameters/limit" + }, + { + "name": "q", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "order_by", + "in": "query", + "schema": { + "$ref": "#/components/schemas/characters search query orderby" + } + }, + { + "name": "sort", + "in": "query", + "schema": { + "$ref": "#/components/schemas/search query sort" + } + }, + { + "name": "letter", + "in": "query", + "description": "Return entries starting with the given letter", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Returns search results for characters", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/characters search" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/users": { + "get": { + "tags": [ + "users" + ], + "operationId": "getUsersSearch", + "parameters": [ + { + "$ref": "#/components/parameters/page" + }, + { + "$ref": "#/components/parameters/limit" + }, + { + "name": "q", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "gender", + "in": "query", + "schema": { + "$ref": "#/components/schemas/users search query gender" + } + }, + { + "name": "location", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "maxAge", + "in": "query", + "schema": { + "type": "integer" + } + }, + { + "name": "minAge", + "in": "query", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Returns search results for users", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/users search" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/userbyid": { + "get": { + "tags": [ + "users" + ], + "operationId": "getUserById", + "responses": { + "200": { + "description": "Returns username by ID search", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/user by id" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/clubs": { + "get": { + "tags": [ + "clubs" + ], + "operationId": "getClubsSearch", + "parameters": [ + { + "$ref": "#/components/parameters/page" + }, + { + "$ref": "#/components/parameters/limit" + }, + { + "name": "q", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "type", + "in": "query", + "schema": { + "$ref": "#/components/schemas/club search query type" + } + }, + { + "name": "category", + "in": "query", + "schema": { + "$ref": "#/components/schemas/club search query category" + } + }, + { + "name": "order_by", + "in": "query", + "schema": { + "$ref": "#/components/schemas/club search query orderby" + } + }, + { + "name": "sort", + "in": "query", + "schema": { + "$ref": "#/components/schemas/search query sort" + } + }, + { + "name": "letter", + "in": "query", + "description": "Return entries starting with the given letter", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Returns search results for clubs", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/clubs search" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/seasons/{year}/{season}": { + "get": { + "tags": [ + "seasons" + ], + "operationId": "getSeason", + "responses": { + "200": { + "description": "Returns seasonal anime", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/anime search" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/seasons": { + "get": { + "tags": [ + "seasons" + ], + "operationId": "getSeasons", + "responses": { + "200": { + "description": "Returns available list of seasons", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/seasons" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/seasons/upcoming": { + "get": { + "tags": [ + "seasons" + ], + "operationId": "getSeasonUpcoming", + "responses": { + "200": { + "description": "Returns upcoming season's anime", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/anime search" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/top/anime": { + "get": { + "tags": [ + "top" + ], + "operationId": "getTopAnime", + "responses": { + "200": { + "description": "Returns top anime", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/anime search" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/top/manga": { + "get": { + "tags": [ + "top" + ], + "operationId": "getTopManga", + "responses": { + "200": { + "description": "Returns top manga", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/manga search" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/top/people": { + "get": { + "tags": [ + "top" + ], + "operationId": "getTopPeople", + "responses": { + "200": { + "description": "Returns top people", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/people search" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/top/characters": { + "get": { + "tags": [ + "top" + ], + "operationId": "getTopCharacters", + "responses": { + "200": { + "description": "Returns top characters", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/characters search" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/top/reviews": { + "get": { + "tags": [ + "top" + ], + "operationId": "getTopReviews", + "responses": { + "200": { + "description": "Returns top reviews", + "content": { + "application/json": { + "schema": { + "properties": { + "data": { + "allOf": [ + { + "properties": { + "data": { + "type": "array", + "items": { + "anyOf": [ + { + "allOf": [ + { + "properties": { + "user": { + "$ref": "#/components/schemas/user meta" + } + }, + "type": "object" + }, + { + "properties": { + "anime": { + "$ref": "#/components/schemas/anime meta" + } + }, + "type": "object" + }, + { + "$ref": "#/components/schemas/anime review" + } + ] + }, + { + "allOf": [ + { + "properties": { + "user": { + "$ref": "#/components/schemas/user meta" + } + }, + "type": "object" + }, + { + "properties": { + "manga": { + "$ref": "#/components/schemas/manga meta" + } + }, + "type": "object" + }, + { + "$ref": "#/components/schemas/manga review" + } + ] + } + ] + } + } + }, + "type": "object" + }, + { + "$ref": "#/components/schemas/pagination" + } + ] + } + }, + "type": "object" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/users/{username}": { + "get": { + "tags": [ + "users" + ], + "operationId": "getUserProfile", + "parameters": [ + { + "name": "username", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Returns user profile", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/user profile" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/users/{username}/statistics": { + "get": { + "tags": [ + "users" + ], + "operationId": "getUserStatistics", + "parameters": [ + { + "name": "username", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Returns user statistics", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/user statistics" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/users/{username}/favorites": { + "get": { + "tags": [ + "users" + ], + "operationId": "getUserFavorites", + "parameters": [ + { + "name": "username", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Returns user favorites", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/user favorites" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/users/{username}/userupdates": { + "get": { + "tags": [ + "users" + ], + "operationId": "getUserUpdates", + "parameters": [ + { + "name": "username", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Returns user updates", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/user updates" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/users/{username}/about": { + "get": { + "tags": [ + "users" + ], + "operationId": "getUserAbout", + "parameters": [ + { + "name": "username", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Returns user about in raw HTML", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/user about" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/users/{username}/history/{type}": { + "get": { + "tags": [ + "users" + ], + "operationId": "getUserHistory", + "parameters": [ + { + "name": "username", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "type", + "in": "path", + "required": false, + "schema": { + "type": "string", + "enum": [ + "anime", + "manga" + ] + } + } + ], + "responses": { + "200": { + "description": "Returns user history (past 30 days)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/user history" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/users/{username}/friends": { + "get": { + "tags": [ + "users" + ], + "operationId": "getUserFriends", + "parameters": [ + { + "name": "username", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Returns user friends", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/user friends" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/users/{username}/animelist": { + "get": { + "tags": [ + "users" + ], + "operationId": "getUserAnimelist", + "parameters": [ + { + "name": "username", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Returns user anime list", + "content": { + "application/json": { + "schema": {} + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/users/{username}/mangalist": { + "get": { + "tags": [ + "users" + ], + "operationId": "getUserMangaList", + "parameters": [ + { + "name": "username", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Returns user manga list", + "content": { + "application/json": { + "schema": {} + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/users/{username}/reviews": { + "get": { + "tags": [ + "users" + ], + "operationId": "getUserReviews", + "parameters": [ + { + "name": "username", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Returns user reviews", + "content": { + "application/json": { + "schema": { + "properties": { + "data": { + "allOf": [ + { + "properties": { + "data": { + "type": "array", + "items": { + "anyOf": [ + { + "allOf": [ + { + "properties": { + "user": { + "$ref": "#/components/schemas/user meta" + } + }, + "type": "object" + }, + { + "properties": { + "anime": { + "$ref": "#/components/schemas/anime meta" + } + }, + "type": "object" + }, + { + "$ref": "#/components/schemas/anime review" + } + ] + }, + { + "allOf": [ + { + "properties": { + "user": { + "$ref": "#/components/schemas/user meta" + } + }, + "type": "object" + }, + { + "properties": { + "manga": { + "$ref": "#/components/schemas/manga meta" + } + }, + "type": "object" + }, + { + "$ref": "#/components/schemas/manga review" + } + ] + } + ] + } + } + }, + "type": "object" + }, + { + "$ref": "#/components/schemas/pagination" + } + ] + } + }, + "type": "object" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/users/{username}/recommendations": { + "get": { + "tags": [ + "users" + ], + "operationId": "getUserRecommendations", + "parameters": [ + { + "name": "username", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Returns Recent Anime Recommendations", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/recommendations" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/users/{username}/clubs": { + "get": { + "tags": [ + "users" + ], + "operationId": "getUserClubs", + "parameters": [ + { + "name": "username", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Returns user clubs", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/user clubs" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/watch/episodes": { + "get": { + "tags": [ + "watch" + ], + "operationId": "getWatchRecentEpisodes", + "responses": { + "200": { + "description": "Returns Recently Added Episodes", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/watch episodes" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/watch/episodes/popular": { + "get": { + "tags": [ + "watch" + ], + "operationId": "getWatchPopularEpisodes", + "responses": { + "200": { + "description": "Returns Popular Episodes", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/watch episodes" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/watch/promos": { + "get": { + "tags": [ + "watch" + ], + "operationId": "getWatchRecentPromos", + "responses": { + "200": { + "description": "Returns Recently Added Promotional Videos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/watch promos" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + }, + "/watch/promos/popular": { + "get": { + "tags": [ + "watch" + ], + "operationId": "getWatchPopularPromos", + "responses": { + "200": { + "description": "Returns Popular Promotional Videos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/watch promos" + } + } + } + }, + "400": { + "description": "Error: Bad request. When required parameters were not supplied." + } + } + } + } + }, + "components": { + "schemas": { + "anime episodes": { + "description": "Anime Episodes Resource", + "allOf": [ + { + "properties": { + "data": { + "type": "array", + "items": { + "properties": { + "mal_id": { + "description": "MyAnimeList ID", + "type": "integer" + }, + "url": { + "description": "MyAnimeList URL", + "type": "string" + }, + "title": { + "description": "Title", + "type": "string" + }, + "title_japanese": { + "description": "Title Japanese", + "type": "string" + }, + "title_romanji": { + "description": "title_romanji", + "type": "string" + }, + "duration": { + "description": "Episode duration in seconds", + "type": "integer" + }, + "aired": { + "description": "Aired Date ISO8601", + "type": "string" + }, + "filler": { + "description": "Filler episode", + "type": "boolean" + }, + "recap": { + "description": "Recap episode", + "type": "boolean" + }, + "forum_url": { + "description": "Episode discussion forum URL", + "type": "string" + } + }, + "type": "object" + } + } + }, + "type": "object" + }, + { + "$ref": "#/components/schemas/pagination" + } + ] + }, + "anime news": { + "description": "Anime News Resource", + "allOf": [ + { + "$ref": "#/components/schemas/pagination" + }, + { + "$ref": "#/components/schemas/news" + } + ] + }, + "character pictures": { + "description": "Character Pictures", + "properties": { + "data": { + "type": "array", + "items": { + "properties": { + "image_url": { + "description": "Default JPG Image Size URL", + "type": "string" + }, + "large_image_url": { + "description": "Large JPG Image Size URL", + "type": "string" + } + }, + "type": "object" + } + } + }, + "type": "object" + }, + "club member": { + "description": "Club Member", + "properties": { + "data": { + "type": "array", + "items": { + "properties": { + "username": { + "description": "MyAnimeList Username", + "type": "string" + }, + "url": { + "description": "MyAnimeList URL", + "type": "string" + }, + "image_url": { + "description": "MyAnimeList Image URL", + "type": "string" + } + }, + "type": "object" + } + } + }, + "type": "object" + }, + "manga news": { + "description": "Manga News Resource", + "allOf": [ + { + "$ref": "#/components/schemas/pagination" + }, + { + "$ref": "#/components/schemas/news" + } + ] + }, + "manga pictures": { + "description": "Manga Pictures", + "properties": { + "data": { + "type": "array", + "items": { + "properties": { + "image_url": { + "description": "Default JPG Image Size URL", + "type": "string" + }, + "large_image_url": { + "description": "Large JPG Image Size URL", + "type": "string" + } + }, + "type": "object" + } + } + }, + "type": "object" + }, + "person pictures": { + "description": "Character Pictures", + "properties": { + "data": { + "type": "array", + "items": { + "properties": { + "image_url": { + "description": "Default JPG Image Size URL", + "type": "string" + }, + "large_image_url": { + "description": "Large JPG Image Size URL", + "type": "string" + } + }, + "type": "object" + } + } + }, + "type": "object" + }, + "random": { + "description": "Random Resources", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "anyOf": [ + { + "$ref": "#/components/schemas/anime" + }, + { + "$ref": "#/components/schemas/manga" + }, + { + "$ref": "#/components/schemas/character" + }, + { + "$ref": "#/components/schemas/person" + } + ] + } + } + }, + "type": "object" + }, + "schedules": { + "description": "Anime resources currently airing", + "allOf": [ + { + "properties": { + "data": { + "type": "array", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/anime" + } + ] + } + } + }, + "type": "object" + }, + { + "$ref": "#/components/schemas/pagination" + } + ] + }, + "search query sort": { + "description": "Characters Search Query Sort", + "type": "string", + "enum": [ + "desc", + "asc" + ] + }, + "users search": { + "description": "User Results", + "allOf": [ + { + "properties": { + "data": { + "type": "array", + "items": { + "type": "object" + } + } + }, + "type": "object" + }, + { + "$ref": "#/components/schemas/pagination" + } + ] + }, + "seasons": { + "description": "List of available seasons", + "properties": { + "data": { + "type": "array", + "items": { + "properties": { + "year": { + "description": "Year", + "type": "integer" + }, + "seasons": { + "description": "List of available seasons", + "type": "array", + "items": { + "type": "string" + } + } + }, + "type": "object" + } + } + }, + "type": "object" + }, + "reviews collection": { + "description": "Anime & Manga Reviews Resource", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "anyOf": [ + { + "$ref": "#/components/schemas/anime review" + }, + { + "$ref": "#/components/schemas/manga review" + } + ] + } + } + }, + "type": "object" + }, + "user friends": { + "description": "User Friends", + "allOf": [ + { + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "allOf": [ + { + "properties": { + "user": { + "$ref": "#/components/schemas/user meta" + } + }, + "type": "object" + }, + { + "properties": { + "last_online": { + "description": "Last Online Date ISO8601 format", + "type": "string" + }, + "friends_since": { + "description": "Friends Since Date ISO8601 format", + "type": "string" + } + }, + "type": "object" + } + ] + } + } + }, + "type": "object" + }, + { + "$ref": "#/components/schemas/pagination" + } + ] + }, + "user clubs": { + "description": "User Clubs", + "allOf": [ + { + "properties": { + "data": { + "type": "array", + "items": { + "properties": { + "mal_id": { + "description": "MyAnimeList ID", + "type": "integer" + }, + "name": { + "description": "Club Name", + "type": "string" + }, + "url": { + "description": "Club URL", + "type": "string" + } + }, + "type": "object" + } + } + }, + "type": "object" + }, + { + "$ref": "#/components/schemas/pagination" + } + ] + }, + "watch episodes": { + "description": "Watch Episodes", + "allOf": [ + { + "properties": { + "data": { + "type": "array", + "items": { + "properties": { + "entry": { + "$ref": "#/components/schemas/anime meta" + }, + "episodes": { + "description": "Recent Episodes (max 2 listed)", + "type": "array", + "items": { + "properties": { + "mal_id": { + "description": "MyAnimeList ID", + "type": "string" + }, + "url": { + "description": "MyAnimeList URL", + "type": "string" + }, + "title": { + "description": "Episode Title", + "type": "string" + }, + "premium": { + "description": "For MyAnimeList Premium Users", + "type": "boolean" + } + }, + "type": "object" + } + }, + "region_locked": { + "description": "Region Locked Episode", + "type": "boolean" + } + }, + "type": "object" + } + } + }, + "type": "object" + }, + { + "$ref": "#/components/schemas/pagination" + } + ] + }, + "watch promos": { + "description": "Watch Promos", + "allOf": [ + { + "$ref": "#/components/schemas/pagination" + }, + { + "allOf": [ + { + "properties": { + "title": { + "description": "Promo Title", + "type": "string" + } + }, + "type": "object" + }, + { + "properties": { + "data": { + "type": "array", + "items": { + "properties": { + "entry": { + "$ref": "#/components/schemas/anime meta" + }, + "trailer": { + "type": "array", + "items": { + "$ref": "#/components/schemas/trailer" + } + } + }, + "type": "object" + } + } + }, + "type": "object" + } + ] + } + ] + }, + "anime search query type": { + "description": "Available Anime types", + "type": "string", + "enum": [ + "tv", + "movie", + "ova", + "special", + "ona", + "music" + ] + }, + "anime search query status": { + "description": "Available Anime statuses", + "type": "string", + "enum": [ + "airing", + "complete", + "upcoming" + ] + }, + "anime search query rating": { + "description": "Available Anime audience ratings

Ratings
", + "type": "string", + "enum": [ + "g", + "pg", + "pg13", + "r17", + "r", + "rx" + ] + }, + "anime search query orderby": { + "description": "Available Anime order_by properties", + "type": "string", + "enum": [ + "mal_id", + "title", + "type", + "rating", + "start_date", + "end_date", + "episodes", + "score", + "scored_by", + "rank", + "popularity", + "members", + "favorites" + ] + }, + "characters search query orderby": { + "description": "Available Character order_by properties", + "type": "string", + "enum": [ + "mal_id", + "name", + "favorites" + ] + }, + "club search query type": { + "description": "Club Search Query Type", + "type": "string", + "enum": [ + "public", + "private", + "secret" + ] + }, + "club search query category": { + "description": "Club Search Query Category", + "type": "string", + "enum": [ + "anime", + "manga", + "actors_and_artists", + "characters", + "cities_and_neighborhoods", + "companies", + "conventions", + "games", + "japan", + "music", + "other", + "schools" + ] + }, + "club search query orderby": { + "description": "Club Search Query OrderBy", + "type": "string", + "enum": [ + "mal_id", + "title", + "members_count", + "pictures_count", + "created" + ] + }, + "magazines query orderby": { + "description": "Order by magazine data", + "type": "string", + "enum": [ + "mal_id", + "name", + "count" + ] + }, + "manga search query type": { + "description": "Available Manga types", + "type": "string", + "enum": [ + "manga", + "novel", + "lightnovel", + "oneshot", + "doujin", + "manhwa", + "manhua" + ] + }, + "manga search query status": { + "description": "Available Manga statuses", + "type": "string", + "enum": [ + "publishing", + "complete", + "hiatus", + "discontinued", + "upcoming" + ] + }, + "manga search query orderby": { + "description": "Available Manga order_by properties", + "type": "string", + "enum": [ + "mal_id", + "title", + "start_date", + "end_date", + "chapters", + "volumes", + "score", + "scored_by", + "rank", + "popularity", + "members", + "favorites" + ] + }, + "people search query orderby": { + "description": "Available People order_by properties", + "type": "string", + "enum": [ + "mal_id", + "name", + "birthday", + "favorites" + ] + }, + "producers query orderby": { + "description": "Order by producers data", + "type": "string", + "enum": [ + "mal_id", + "name", + "count" + ] + }, + "users search query gender": { + "description": "Users Search Query Gender", + "type": "string", + "enum": [ + "any", + "male", + "female", + "nonbinary" + ] + }, + "anime characters": { + "description": "Anime Characters Resource", + "properties": { + "data": { + "type": "array", + "items": { + "properties": { + "character": { + "description": "Character details", + "properties": { + "mal_id": { + "description": "MyAnimeList ID", + "type": "integer" + }, + "url": { + "description": "MyAnimeList URL", + "type": "string" + }, + "images": { + "$ref": "#/components/schemas/character images" + }, + "name": { + "description": "Character Name", + "type": "string" + } + }, + "type": "object" + }, + "role": { + "description": "Character's Role", + "type": "string" + }, + "voice_actors": { + "type": "array", + "items": { + "properties": { + "person": { + "properties": { + "mal_id": { + "type": "integer" + }, + "url": { + "type": "string" + }, + "images": { + "$ref": "#/components/schemas/people images" + }, + "name": { + "type": "string" + } + }, + "type": "object" + }, + "language": { + "type": "string" + } + }, + "type": "object" + } + } + }, + "type": "object" + } + } + }, + "type": "object" + }, + "anime search": { + "description": "Anime Collection Resource", + "allOf": [ + { + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/anime" + } + } + }, + "type": "object" + }, + { + "$ref": "#/components/schemas/pagination" + } + ] + }, + "anime episode": { + "description": "Anime Episode Resource", + "properties": { + "data": { + "properties": { + "mal_id": { + "description": "MyAnimeList ID", + "type": "integer" + }, + "url": { + "description": "MyAnimeList URL", + "type": "string" + }, + "title": { + "description": "Title", + "type": "string" + }, + "title_japanese": { + "description": "Title Japanese", + "type": "string" + }, + "title_romanji": { + "description": "title_romanji", + "type": "string" + }, + "duration": { + "description": "Episode duration in seconds", + "type": "integer" + }, + "aired": { + "description": "Aired Date ISO8601", + "type": "string" + }, + "filler": { + "description": "Filler episode", + "type": "boolean" + }, + "recap": { + "description": "Recap episode", + "type": "boolean" + }, + "synopsis": { + "description": "Episode Synopsis", + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "anime relations": { + "description": "Anime Relations", + "properties": { + "data": { + "type": "array", + "items": { + "properties": { + "relation": { + "description": "Relation type", + "type": "string" + }, + "entry": { + "type": "array", + "items": { + "$ref": "#/components/schemas/mal_url" + } + } + }, + "type": "object" + } + } + }, + "type": "object" + }, + "anime": { + "description": "Anime Resource", + "properties": { + "mal_id": { + "description": "MyAnimeList ID", + "type": "integer" + }, + "url": { + "description": "MyAnimeList URL", + "type": "string" + }, + "images": { + "$ref": "#/components/schemas/anime images" + }, + "trailer": { + "$ref": "#/components/schemas/trailer base" + }, + "title": { + "description": "Title", + "type": "string" + }, + "title_english": { + "description": "English Title", + "type": "string" + }, + "title_japanese": { + "description": "Japanese Title", + "type": "string" + }, + "title_synonyms": { + "description": "Other Titles", + "type": "array", + "items": { + "type": "string" + } + }, + "type": { + "description": "Anime Type", + "type": "string", + "enum": [ + "TV", + "OVA", + "Movie", + "Special", + "ONA", + "Music" + ] + }, + "source": { + "description": "Original Material/Source adapted from", + "type": "string" + }, + "episodes": { + "description": "Episode count", + "type": "integer" + }, + "status": { + "description": "Airing status", + "type": "string", + "enum": [ + "Finished Airing", + "Currently Airing", + "Not yet aired" + ] + }, + "airing": { + "description": "Airing boolean", + "type": "boolean" + }, + "aired": { + "$ref": "#/components/schemas/daterange" + }, + "duration": { + "description": "Parsed raw duration", + "type": "string" + }, + "rating": { + "description": "Anime audience rating", + "type": "string", + "enum": [ + "G - All Ages", + "PG - Children", + "PG-13 - Teens 13 or older", + "R - 17+ (violence & profanity)", + "R+ - Mild Nudity", + "Rx - Hentai" + ] + }, + "score": { + "description": "Score", + "type": "number", + "format": "float" + }, + "scored_by": { + "description": "Number of users", + "type": "integer" + }, + "rank": { + "description": "Ranking", + "type": "integer" + }, + "popularity": { + "description": "Popularity", + "type": "integer" + }, + "members": { + "description": "Number of users who have added this entry to their list", + "type": "integer" + }, + "favorites": { + "description": "Number of users who have favorited this entry", + "type": "integer" + }, + "synopsis": { + "description": "Synopsis", + "type": "string" + }, + "background": { + "description": "Background", + "type": "string" + }, + "season": { + "description": "Season", + "type": "string", + "enum": [ + "Summer", + "Winter", + "Spring", + "Fall" + ] + }, + "year": { + "description": "Year", + "type": "integer" + }, + "broadcast": { + "$ref": "#/components/schemas/broadcast" + }, + "producers": { + "type": "array", + "items": { + "$ref": "#/components/schemas/mal_url" + } + }, + "licensors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/mal_url" + } + }, + "studios": { + "type": "array", + "items": { + "$ref": "#/components/schemas/mal_url" + } + }, + "genres": { + "type": "array", + "items": { + "$ref": "#/components/schemas/mal_url" + } + }, + "explicit_genres": { + "type": "array", + "items": { + "$ref": "#/components/schemas/mal_url" + } + }, + "themes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/mal_url" + } + }, + "demographics": { + "type": "array", + "items": { + "$ref": "#/components/schemas/mal_url" + } + } + }, + "type": "object" + }, + "anime staff": { + "description": "Anime Staff Resource", + "properties": { + "data": { + "type": "array", + "items": { + "properties": { + "person": { + "description": "Person details", + "properties": { + "mal_id": { + "description": "MyAnimeList ID", + "type": "integer" + }, + "url": { + "description": "MyAnimeList URL", + "type": "string" + }, + "images": { + "$ref": "#/components/schemas/people images" + }, + "name": { + "description": "Name", + "type": "string" + } + }, + "type": "object" + }, + "positions": { + "description": "Staff Positions", + "type": "array", + "items": { + "type": "string" + } + } + }, + "type": "object" + } + } + }, + "type": "object" + }, + "anime statistics": { + "description": "Anime Statistics Resource", + "properties": { + "data": { + "properties": { + "watching": { + "description": "Number of users watching the resource", + "type": "integer" + }, + "completed": { + "description": "Number of users who have completed the resource", + "type": "integer" + }, + "on_hold": { + "description": "Number of users who have put the resource on hold", + "type": "integer" + }, + "dropped": { + "description": "Number of users who have dropped the resource", + "type": "integer" + }, + "plan_to_watch": { + "description": "Number of users who have planned to watch the resource", + "type": "integer" + }, + "total": { + "description": "Total number of users who have the resource added to their lists", + "type": "integer" + }, + "scores": { + "type": "array", + "items": { + "properties": { + "score": { + "description": "Scoring value", + "type": "integer" + }, + "votes": { + "description": "Number of votes for this score", + "type": "integer" + }, + "percentage": { + "description": "Percentage of votes for this score", + "type": "number", + "format": "float" + } + }, + "type": "object" + } + } + }, + "type": "object" + } + }, + "type": "object" + }, + "anime themes": { + "description": "Anime Opening and Ending Themes", + "properties": { + "data": { + "properties": { + "openings": { + "type": "array", + "items": { + "type": "string" + } + }, + "endings": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "type": "object" + } + }, + "type": "object" + }, + "anime videos": { + "description": "Anime Videos Resource", + "properties": { + "data": { + "properties": { + "promos": { + "type": "array", + "items": { + "properties": { + "title": { + "description": "Title", + "type": "string" + }, + "trailer": { + "$ref": "#/components/schemas/trailer" + } + }, + "type": "object" + } + }, + "episodes": { + "type": "array", + "items": { + "properties": { + "mal_id": { + "description": "MyAnimeList ID", + "type": "integer" + }, + "url": { + "description": "MyAnimeList URL", + "type": "string" + }, + "title": { + "description": "Title", + "type": "string" + }, + "episode": { + "description": "Episode", + "type": "string" + }, + "images": { + "$ref": "#/components/schemas/common images" + } + }, + "type": "object" + } + } + }, + "type": "object" + } + }, + "type": "object" + }, + "character anime": { + "description": "Character casted in anime", + "properties": { + "data": { + "type": "array", + "items": { + "properties": { + "role": { + "description": "Character's Role", + "type": "string" + }, + "anime": { + "$ref": "#/components/schemas/anime meta" + } + }, + "type": "object" + } + } + }, + "type": "object" + }, + "characters search": { + "description": "Characters Search Resource", + "allOf": [ + { + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/character" + } + } + }, + "type": "object" + }, + { + "$ref": "#/components/schemas/pagination" + } + ] + }, + "character manga": { + "description": "Character casted in manga", + "properties": { + "data": { + "type": "array", + "items": { + "properties": { + "role": { + "description": "Character's Role", + "type": "string" + }, + "manga": { + "$ref": "#/components/schemas/manga meta" + } + }, + "type": "object" + } + } + }, + "type": "object" + }, + "character": { + "description": "Character Resource", + "properties": { + "mal_id": { + "description": "MyAnimeList ID", + "type": "integer" + }, + "url": { + "description": "MyAnimeList URL", + "type": "string" + }, + "images": { + "$ref": "#/components/schemas/character images" + }, + "name": { + "description": "Name", + "type": "string" + }, + "nicknames": { + "description": "Other Names", + "type": "array", + "items": { + "type": "string" + } + }, + "favorites": { + "description": "Number of users who have favorited this entry", + "type": "integer" + }, + "about": { + "description": "Synopsis", + "type": "string" + }, + "animeography": { + "type": "array", + "items": { + "type": "object", + "allOf": [ + { + "properties": { + "image_url": { + "type": "string" + }, + "role": { + "type": "string" + } + }, + "type": "object" + }, + { + "$ref": "#/components/schemas/mal_url" + } + ] + } + }, + "mangaography": { + "type": "array", + "items": { + "type": "object", + "allOf": [ + { + "properties": { + "image_url": { + "type": "string" + }, + "role": { + "type": "string" + } + }, + "type": "object" + }, + { + "$ref": "#/components/schemas/mal_url" + } + ] + } + }, + "voice_actors": { + "type": "array", + "items": { + "type": "object", + "allOf": [ + { + "properties": { + "image_url": { + "type": "string" + }, + "language": { + "type": "string" + } + }, + "type": "object" + }, + { + "$ref": "#/components/schemas/mal_url" + } + ] + } + } + }, + "type": "object" + }, + "character voice actors": { + "description": "Character voice actors", + "properties": { + "data": { + "type": "array", + "items": { + "properties": { + "language": { + "description": "Character's Role", + "type": "string" + }, + "person": { + "$ref": "#/components/schemas/person meta" + } + }, + "type": "object" + } + } + }, + "type": "object" + }, + "clubs search": { + "description": "Clubs Search Resource", + "allOf": [ + { + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/club" + } + } + }, + "type": "object" + }, + { + "$ref": "#/components/schemas/pagination" + } + ] + }, + "club relations": { + "description": "Club Relations", + "properties": { + "data": { + "properties": { + "anime": { + "type": "array", + "items": { + "properties": { + "": { + "$ref": "#/components/schemas/mal_url" + } + }, + "type": "object" + } + }, + "manga": { + "type": "array", + "items": { + "properties": { + "": { + "$ref": "#/components/schemas/mal_url" + } + }, + "type": "object" + } + }, + "characters": { + "type": "array", + "items": { + "properties": { + "": { + "$ref": "#/components/schemas/mal_url" + } + }, + "type": "object" + } + } + }, + "type": "object" + } + }, + "type": "object" + }, + "club": { + "description": "Club Resource", + "properties": { + "data": { + "properties": { + "mal_id": { + "description": "MyAnimeList ID", + "type": "integer" + }, + "name": { + "description": "Club name", + "type": "string" + }, + "url": { + "description": "Club URL", + "type": "string" + }, + "images": { + "$ref": "#/components/schemas/common images" + }, + "members": { + "description": "Number of club members", + "type": "integer" + }, + "category": { + "description": "Club Category", + "type": "string", + "enum": [ + "actors & artists", + "anime", + "characters", + "cities & neighborhoods", + "companies", + "conventions", + "games", + "japan", + "manga", + "music", + "others", + "schools" + ] + }, + "created": { + "description": "Date Created ISO8601", + "type": "string" + }, + "access": { + "description": "Club access", + "type": "string", + "enum": [ + "public", + "private", + "secret" + ] + } + }, + "type": "object" + } + }, + "type": "object" + }, + "club staff": { + "description": "Club Staff Resource", + "properties": { + "data": { + "type": "array", + "items": { + "properties": { + "url": { + "description": "User URL", + "type": "string" + }, + "username": { + "description": "User's username", + "type": "string" + } + }, + "type": "object" + } + } + }, + "type": "object" + }, + "trailer": { + "description": "Youtube Details", + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/trailer base" + }, + { + "$ref": "#/components/schemas/trailer images" + } + ] + }, + "trailer base": { + "description": "Youtube Details", + "properties": { + "youtube_id": { + "description": "YouTube ID", + "type": "string" + }, + "url": { + "description": "YouTube URL", + "type": "string" + }, + "embed_url": { + "description": "Parsed Embed URL", + "type": "string" + } + }, + "type": "object" + }, + "trailer images": { + "description": "Youtube Images", + "properties": { + "images": { + "properties": { + "default_image_url": { + "description": "Default Image Size URL (120x90)", + "type": "string" + }, + "small_image_url": { + "description": "Small Image Size URL (640x480)", + "type": "string" + }, + "medium_image_url": { + "description": "Medium Image Size URL (320x180)", + "type": "string" + }, + "large_image_url": { + "description": "Large Image Size URL (480x360)", + "type": "string" + }, + "maximum_image_url": { + "description": "Maximum Image Size URL (1280x720)", + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "daterange": { + "description": "Date range", + "properties": { + "from": { + "description": "Date ISO8601", + "type": "string" + }, + "to": { + "description": "Date ISO8601", + "type": "string" + }, + "prop": { + "description": "Date Prop", + "properties": { + "from": { + "description": "Date Prop From", + "properties": { + "day": { + "description": "Day", + "type": "integer" + }, + "month": { + "description": "Month", + "type": "integer" + }, + "year": { + "description": "year", + "type": "integer" + } + }, + "type": "object" + }, + "to": { + "description": "Date Prop To", + "properties": { + "day": { + "description": "Day", + "type": "integer" + }, + "month": { + "description": "Month", + "type": "integer" + }, + "year": { + "description": "year", + "type": "integer" + } + }, + "type": "object" + }, + "string": { + "description": "Raw parsed string", + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "broadcast": { + "description": "Broadcast Details", + "properties": { + "day": { + "description": "Day of the week", + "type": "string" + }, + "time": { + "description": "Time in 24 hour format", + "type": "string" + }, + "timezone": { + "description": "Timezone (Tz Database format https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)", + "type": "string" + }, + "string": { + "description": "Raw parsed broadcast string", + "type": "string" + } + }, + "type": "object" + }, + "mal_url": { + "description": "Parsed URL Data", + "properties": { + "mal_id": { + "description": "MyAnimeList ID", + "type": "integer" + }, + "type": { + "description": "Type of resource", + "type": "string" + }, + "name": { + "description": "Resource Name/Title", + "type": "string" + }, + "url": { + "description": "MyAnimeList URL", + "type": "string" + } + }, + "type": "object" + }, + "mal_url_2": { + "description": "Parsed URL Data", + "properties": { + "mal_id": { + "description": "MyAnimeList ID", + "type": "integer" + }, + "type": { + "description": "Type of resource", + "type": "string" + }, + "title": { + "description": "Resource Name/Title", + "type": "string" + }, + "url": { + "description": "MyAnimeList URL", + "type": "string" + } + }, + "type": "object" + }, + "entry_meta": { + "description": "Entry Meta data", + "properties": { + "mal_id": { + "description": "MyAnimeList ID", + "type": "integer" + }, + "url": { + "description": "MyAnimeList URL", + "type": "string" + }, + "image_url": { + "description": "Image URL", + "type": "string" + }, + "name": { + "description": "Entry Name/Title", + "type": "string" + } + }, + "type": "object" + }, + "relation": { + "description": "Related resources", + "type": "array", + "items": { + "properties": { + "relation": { + "description": "Relation type", + "type": "string" + }, + "items": { + "description": "Related items", + "type": "array", + "items": { + "$ref": "#/components/schemas/mal_url" + } + } + }, + "type": "object" + } + }, + "pagination": { + "properties": { + "pagination": { + "properties": { + "last_visible_page": { + "type": "integer" + }, + "has_next_page": { + "type": "boolean" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "user meta": { + "properties": { + "username": { + "description": "MyAnimeList Username", + "type": "string" + }, + "url": { + "description": "MyAnimeList Profile URL", + "type": "string" + }, + "images": { + "$ref": "#/components/schemas/user images" + } + }, + "type": "object" + }, + "user by id": { + "description": "User Meta By ID", + "properties": { + "data": { + "properties": { + "url": { + "description": "MyAnimeList URL", + "type": "string" + }, + "username": { + "description": "MyAnimeList Username", + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "user images": { + "properties": { + "jpg": { + "description": "Available images in JPG", + "properties": { + "image_url": { + "description": "Image URL JPG (225x335)", + "type": "string" + } + }, + "type": "object" + }, + "webp": { + "description": "Available images in WEBP", + "properties": { + "image_url": { + "description": "Image URL WEBP (225x335)", + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "anime meta": { + "properties": { + "mal_id": { + "description": "MyAnimeList ID", + "type": "integer" + }, + "url": { + "description": "MyAnimeList URL", + "type": "string" + }, + "images": { + "$ref": "#/components/schemas/anime images" + }, + "title": { + "description": "Entry title", + "type": "string" + } + }, + "type": "object" + }, + "manga meta": { + "properties": { + "mal_id": { + "description": "MyAnimeList ID", + "type": "integer" + }, + "url": { + "description": "MyAnimeList URL", + "type": "string" + }, + "images": { + "$ref": "#/components/schemas/manga images" + }, + "title": { + "description": "Entry title", + "type": "string" + } + }, + "type": "object" + }, + "character meta": { + "properties": { + "mal_id": { + "description": "MyAnimeList ID", + "type": "integer" + }, + "url": { + "description": "MyAnimeList URL", + "type": "string" + }, + "images": { + "$ref": "#/components/schemas/character images" + }, + "name": { + "description": "Entry name", + "type": "string" + } + }, + "type": "object" + }, + "person meta": { + "properties": { + "mal_id": { + "description": "MyAnimeList ID", + "type": "integer" + }, + "url": { + "description": "MyAnimeList URL", + "type": "string" + }, + "images": { + "$ref": "#/components/schemas/people images" + }, + "name": { + "description": "Entry name", + "type": "string" + } + }, + "type": "object" + }, + "anime images": { + "properties": { + "jpg": { + "description": "Available images in JPG", + "properties": { + "image_url": { + "description": "Image URL JPG (225x335)", + "type": "string" + }, + "small_image_url": { + "description": "Small Image URL JPG (50x74)", + "type": "string" + }, + "large_image_url": { + "description": "Image URL JPG (300x446)", + "type": "string" + } + }, + "type": "object" + }, + "webp": { + "description": "Available images in WEBP", + "properties": { + "image_url": { + "description": "Image URL WEBP (225x335)", + "type": "string" + }, + "small_image_url": { + "description": "Small Image URL WEBP (50x74)", + "type": "string" + }, + "large_image_url": { + "description": "Image URL WEBP (300x446)", + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "manga images": { + "properties": { + "jpg": { + "description": "Available images in JPG", + "properties": { + "image_url": { + "description": "Image URL JPG (225x335)", + "type": "string" + }, + "small_image_url": { + "description": "Small Image URL JPG (50x74)", + "type": "string" + }, + "large_image_url": { + "description": "Image URL JPG (300x446)", + "type": "string" + } + }, + "type": "object" + }, + "webp": { + "description": "Available images in WEBP", + "properties": { + "image_url": { + "description": "Image URL WEBP (225x335)", + "type": "string" + }, + "small_image_url": { + "description": "Small Image URL WEBP (50x74)", + "type": "string" + }, + "large_image_url": { + "description": "Image URL WEBP (300x446)", + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "character images": { + "properties": { + "jpg": { + "description": "Available images in JPG", + "properties": { + "image_url": { + "description": "Image URL JPG (225x335)", + "type": "string" + }, + "small_image_url": { + "description": "Small Image URL JPG (50x74)", + "type": "string" + } + }, + "type": "object" + }, + "webp": { + "description": "Available images in WEBP", + "properties": { + "image_url": { + "description": "Image URL WEBP (225x335)", + "type": "string" + }, + "small_image_url": { + "description": "Small Image URL WEBP (50x74)", + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "people images": { + "properties": { + "jpg": { + "description": "Available images in JPG", + "properties": { + "image_url": { + "description": "Image URL JPG (225x335)", + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "common images": { + "properties": { + "jpg": { + "description": "Available images in JPG", + "properties": { + "image_url": { + "description": "Image URL JPG (225x335)", + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "forum": { + "description": "Forum Resource", + "properties": { + "data": { + "type": "array", + "items": { + "properties": { + "mal_id": { + "description": "MyAnimeList ID", + "type": "integer" + }, + "url": { + "description": "MyAnimeList URL", + "type": "string" + }, + "title": { + "description": "Title", + "type": "string" + }, + "date": { + "description": "Post Date ISO8601", + "type": "string" + }, + "author_username": { + "description": "Author MyAnimeList Username", + "type": "string" + }, + "author_url": { + "description": "Author Profile URL", + "type": "string" + }, + "comments": { + "description": "Comment count", + "type": "integer" + }, + "last_comment": { + "description": "Last comment details", + "properties": { + "url": { + "description": "Last comment URL", + "type": "string" + }, + "author_username": { + "description": "Author MyAnimeList Username", + "type": "string" + }, + "author_url": { + "description": "Author Profile URL", + "type": "string" + }, + "date": { + "description": "Last comment date posted ISO8601", + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + } + } + }, + "type": "object" + }, + "genres": { + "description": "Genres Collection Resource", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/genre" + } + } + }, + "type": "object" + }, + "genre query filter": { + "description": "Filter genres by type", + "type": "string", + "enum": [ + "genres", + "explicit_genres", + "themes", + "demographics" + ] + }, + "genre": { + "description": "Genre Resource", + "properties": { + "mal_id": { + "description": "MyAnimeList ID", + "type": "integer" + }, + "name": { + "description": "Genre Name", + "type": "string" + }, + "url": { + "description": "MyAnimeList URL", + "type": "string" + }, + "count": { + "description": "Genre's entry count", + "type": "integer" + } + }, + "type": "object" + }, + "magazines": { + "description": "Magazine Collection Resource", + "properties": { + "data": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/pagination" + }, + { + "$ref": "#/components/schemas/magazine" + } + ] + } + }, + "type": "object" + }, + "magazine": { + "description": "Magazine Resource", + "properties": { + "mal_id": { + "description": "MyAnimeList ID", + "type": "integer" + }, + "name": { + "description": "Magazine Name", + "type": "string" + }, + "url": { + "description": "MyAnimeList URL", + "type": "string" + }, + "count": { + "description": "Magazine's manga count", + "type": "integer" + } + }, + "type": "object" + }, + "manga characters": { + "description": "Manga Characters Resource", + "properties": { + "data": { + "type": "array", + "items": { + "properties": { + "character": { + "$ref": "#/components/schemas/character meta" + }, + "role": { + "description": "Character's Role", + "type": "string" + } + }, + "type": "object" + } + } + }, + "type": "object" + }, + "manga search": { + "description": "Manga Search Resource", + "allOf": [ + { + "properties": { + "data": { + "type": "array", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/manga" + } + ] + } + } + }, + "type": "object" + }, + { + "$ref": "#/components/schemas/pagination" + } + ] + }, + "manga": { + "description": "Manga Resource", + "properties": { + "mal_id": { + "description": "MyAnimeList ID", + "type": "integer" + }, + "url": { + "description": "MyAnimeList URL", + "type": "string" + }, + "images": { + "$ref": "#/components/schemas/manga images" + }, + "title": { + "description": "Title", + "type": "string" + }, + "title_english": { + "description": "English Title", + "type": "string" + }, + "title_japanese": { + "description": "Japanese Title", + "type": "string" + }, + "title_synonyms": { + "description": "Other Titles", + "type": "array", + "items": { + "type": "string" + } + }, + "type": { + "description": "Manga Type", + "type": "string", + "enum": [ + "Manga", + "Novel", + "One-shot", + "Doujinshi", + "Manhua", + "Manhwa", + "OEL" + ] + }, + "chapters": { + "description": "Chapter count", + "type": "integer" + }, + "volumnes": { + "description": "Volume count", + "type": "integer" + }, + "status": { + "description": "Publishing status", + "type": "string", + "enum": [ + "Finished", + "Publishing", + "On Hiatus", + "Discontinued", + "Not yet published" + ] + }, + "publishing": { + "description": "Publishing boolean", + "type": "boolean" + }, + "published": { + "$ref": "#/components/schemas/daterange" + }, + "score": { + "description": "Score", + "type": "number", + "format": "float" + }, + "scored_by": { + "description": "Number of users", + "type": "integer" + }, + "rank": { + "description": "Ranking", + "type": "integer" + }, + "popularity": { + "description": "Popularity", + "type": "integer" + }, + "members": { + "description": "Number of users who have added this entry to their list", + "type": "integer" + }, + "favorites": { + "description": "Number of users who have favorited this entry", + "type": "integer" + }, + "synopsis": { + "description": "Synopsis", + "type": "string" + }, + "background": { + "description": "Background", + "type": "string" + }, + "authors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/mal_url" + } + }, + "serializations": { + "type": "array", + "items": { + "$ref": "#/components/schemas/mal_url" + } + }, + "genres": { + "type": "array", + "items": { + "$ref": "#/components/schemas/mal_url" + } + }, + "explicit_genres": { + "type": "array", + "items": { + "$ref": "#/components/schemas/mal_url" + } + }, + "themes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/mal_url" + } + }, + "demographics": { + "type": "array", + "items": { + "$ref": "#/components/schemas/mal_url" + } + } + }, + "type": "object" + }, + "manga statistics": { + "description": "Manga Statistics Resource", + "properties": { + "data": { + "properties": { + "reading": { + "description": "Number of users reading the resource", + "type": "integer" + }, + "completed": { + "description": "Number of users who have completed the resource", + "type": "integer" + }, + "on_hold": { + "description": "Number of users who have put the resource on hold", + "type": "integer" + }, + "dropped": { + "description": "Number of users who have dropped the resource", + "type": "integer" + }, + "plan_to_read": { + "description": "Number of users who have planned to read the resource", + "type": "integer" + }, + "total": { + "description": "Total number of users who have the resource added to their lists", + "type": "integer" + }, + "scores": { + "type": "array", + "items": { + "properties": { + "score": { + "description": "Scoring value", + "type": "integer" + }, + "votes": { + "description": "Number of votes for this score", + "type": "integer" + }, + "percentage": { + "description": "Percentage of votes for this score", + "type": "number", + "format": "float" + } + }, + "type": "object" + } + } + }, + "type": "object" + } + }, + "type": "object" + }, + "moreinfo": { + "description": "More Info Resource", + "properties": { + "data": { + "properties": { + "moreinfo": { + "description": "Additional information on the entry", + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "news": { + "properties": { + "data": { + "type": "array", + "items": { + "properties": { + "mal_id": { + "description": "MyAnimeList ID", + "type": "integer" + }, + "url": { + "description": "MyAnimeList URL", + "type": "string" + }, + "title": { + "description": "Title", + "type": "string" + }, + "date": { + "description": "Post Date ISO8601", + "type": "string" + }, + "author_username": { + "description": "Author MyAnimeList Username", + "type": "string" + }, + "author_url": { + "description": "Author Profile URL", + "type": "string" + }, + "forum_url": { + "description": "Forum topic URL", + "type": "string" + }, + "images": { + "$ref": "#/components/schemas/common images" + }, + "comments": { + "description": "Comment count", + "type": "integer" + }, + "excerpt": { + "description": "Excerpt", + "type": "string" + } + }, + "type": "object" + } + } + }, + "type": "object" + }, + "person anime": { + "description": "Person anime staff positions", + "properties": { + "data": { + "type": "array", + "items": { + "properties": { + "position": { + "description": "Person's position", + "type": "string" + }, + "anime": { + "$ref": "#/components/schemas/anime meta" + } + }, + "type": "object" + } + } + }, + "type": "object" + }, + "people search": { + "description": "People Search", + "allOf": [ + { + "properties": { + "data": { + "type": "array", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/person" + } + ] + } + } + }, + "type": "object" + }, + { + "$ref": "#/components/schemas/pagination" + } + ] + }, + "person manga": { + "description": "Person's mangaography", + "properties": { + "data": { + "type": "array", + "items": { + "properties": { + "position": { + "description": "Person's position", + "type": "string" + }, + "manga": { + "$ref": "#/components/schemas/manga meta" + } + }, + "type": "object" + } + } + }, + "type": "object" + }, + "person": { + "description": "Person Resource", + "properties": { + "mal_id": { + "description": "MyAnimeList ID", + "type": "integer" + }, + "url": { + "description": "MyAnimeList URL", + "type": "string" + }, + "website_url": { + "description": "Person's website URL", + "type": "string" + }, + "images": { + "$ref": "#/components/schemas/people images" + }, + "name": { + "description": "Name", + "type": "string" + }, + "given_name": { + "description": "Given Name", + "type": "string" + }, + "family_name": { + "description": "Family Name", + "type": "string" + }, + "alternate_names": { + "description": "Other Names", + "type": "array", + "items": { + "type": "string" + } + }, + "birthday": { + "description": "Birthday Date ISO8601", + "type": "string" + }, + "favorites": { + "description": "Number of users who have favorited this entry", + "type": "integer" + }, + "about": { + "description": "Biography", + "type": "string" + } + }, + "type": "object" + }, + "person voice acting roles": { + "description": "Person's voice acting roles", + "properties": { + "data": { + "type": "array", + "items": { + "properties": { + "role": { + "description": "Person's Character's role in the anime", + "type": "string" + }, + "anime": { + "$ref": "#/components/schemas/anime meta" + }, + "character": { + "$ref": "#/components/schemas/character meta" + } + }, + "type": "object" + } + } + }, + "type": "object" + }, + "pictures": { + "description": "Pictures Resource", + "properties": { + "data": { + "type": "array", + "items": { + "properties": { + "images": { + "$ref": "#/components/schemas/anime images" + } + }, + "type": "object" + } + } + }, + "type": "object" + }, + "pictures variants": { + "description": "Pictures Resource", + "properties": { + "data": { + "type": "array", + "items": { + "properties": { + "images": { + "$ref": "#/components/schemas/common images" + } + }, + "type": "object" + } + } + }, + "type": "object" + }, + "producers": { + "description": "Producer Collection Resource", + "properties": { + "data": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/pagination" + }, + { + "$ref": "#/components/schemas/producer" + } + ] + } + }, + "type": "object" + }, + "producer": { + "description": "Producer Resource", + "properties": { + "mal_id": { + "description": "MyAnimeList ID", + "type": "integer" + }, + "name": { + "description": "Producer Name", + "type": "string" + }, + "url": { + "description": "MyAnimeList URL", + "type": "string" + }, + "count": { + "description": "Producer's anime count", + "type": "integer" + } + }, + "type": "object" + }, + "user about": { + "properties": { + "data": { + "type": "array", + "items": { + "properties": { + "about": { + "description": "User About. NOTE: About information is customizable by users through BBCode on MyAnimeList. This means users can add multimedia content, different text sizes, etc. Due to this freeform, Jikan returns parsed HTML. Validate on your end!", + "type": "string" + } + }, + "type": "object" + } + } + }, + "type": "object" + }, + "user favorites": { + "properties": { + "data": { + "description": "Favorite entries", + "properties": { + "anime": { + "description": "Favorite Anime", + "type": "array", + "items": { + "type": "object", + "allOf": [ + { + "properties": { + "type": { + "type": "string" + }, + "start_year": { + "type": "integer" + } + }, + "type": "object" + }, + { + "$ref": "#/components/schemas/anime meta" + } + ] + } + }, + "manga": { + "description": "Favorite Manga", + "type": "array", + "items": { + "type": "object", + "allOf": [ + { + "properties": { + "type": { + "type": "string" + }, + "start_year": { + "type": "integer" + } + }, + "type": "object" + }, + { + "$ref": "#/components/schemas/manga meta" + } + ] + } + }, + "characters": { + "description": "Favorite Characters", + "type": "array", + "items": { + "type": "object", + "allOf": [ + { + "properties": { + "": { + "$ref": "#/components/schemas/mal_url_2" + } + }, + "type": "object" + }, + { + "$ref": "#/components/schemas/character meta" + } + ] + } + }, + "people": { + "description": "Favorite People", + "type": "array", + "items": { + "properties": { + "": { + "$ref": "#/components/schemas/character meta" + } + }, + "type": "object" + } + } + }, + "type": "object" + } + }, + "type": "object" + }, + "user history": { + "properties": { + "data": { + "type": "array", + "items": { + "type": "object" + } + } + }, + "type": "object" + }, + "history": { + "description": "Transform the resource into an array.", + "properties": { + "entry": { + "$ref": "#/components/schemas/mal_url" + }, + "increment": { + "description": "Number of episodes/chapters watched/read", + "type": "integer" + }, + "date": { + "description": "Date ISO8601", + "type": "string" + } + }, + "type": "object" + }, + "user updates": { + "properties": { + "data": { + "properties": { + "anime": { + "description": "Last updated Anime", + "type": "array", + "items": { + "type": "object", + "allOf": [ + { + "properties": { + "entry": { + "$ref": "#/components/schemas/anime meta" + } + }, + "type": "object" + }, + { + "properties": { + "score": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "episodes_seen": { + "type": "integer" + }, + "episodes_total": { + "type": "integer" + }, + "date": { + "description": "ISO8601 format", + "type": "string" + } + }, + "type": "object" + } + ] + } + }, + "manga": { + "description": "Last updated Manga", + "type": "array", + "items": { + "type": "object", + "allOf": [ + { + "properties": { + "entry": { + "$ref": "#/components/schemas/manga meta" + } + }, + "type": "object" + }, + { + "properties": { + "score": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "chapters_read": { + "type": "integer" + }, + "chapters_total": { + "type": "integer" + }, + "volumes_read": { + "type": "integer" + }, + "volumes_total": { + "type": "integer" + }, + "date": { + "description": "ISO8601 format", + "type": "string" + } + }, + "type": "object" + } + ] + } + } + }, + "type": "object" + } + }, + "type": "object" + }, + "user profile": { + "properties": { + "mal_id": { + "description": "MyAnimeList ID", + "type": "integer" + }, + "username": { + "description": "MyAnimeList Username", + "type": "string" + }, + "url": { + "description": "MyAnimeList URL", + "type": "string" + }, + "images": { + "$ref": "#/components/schemas/user images" + }, + "last_online": { + "description": "Last Online Date ISO8601", + "type": "string" + }, + "gender": { + "description": "User Gender", + "type": "string" + }, + "birthday": { + "description": "Birthday Date ISO8601", + "type": "string" + }, + "location": { + "description": "Location", + "type": "string" + }, + "joined": { + "description": "Joined Date ISO8601", + "type": "string" + } + }, + "type": "object" + }, + "users temp": { + "description": "Transform the resource into an array.", + "properties": { + "data": { + "type": "array", + "items": { + "properties": { + "mal_id": { + "description": "MyAnimeList ID", + "type": "integer" + }, + "username": { + "description": "MyAnimeList Username", + "type": "string" + }, + "url": { + "description": "MyAnimeList URL", + "type": "string" + }, + "images": { + "description": "Images", + "properties": { + "jpg": { + "description": "Available images in JPG", + "properties": { + "image_url": { + "description": "Image URL JPG (225x335)", + "type": "string" + } + }, + "type": "object" + }, + "webp": { + "description": "Available images in WEBP", + "properties": { + "image_url": { + "description": "Image URL WEBP (225x335)", + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "last_online": { + "description": "Last Online Date ISO8601", + "type": "string" + }, + "gender": { + "description": "User Gender", + "type": "string" + }, + "birthday": { + "description": "Birthday Date ISO8601", + "type": "string" + }, + "location": { + "description": "Location", + "type": "string" + }, + "joined": { + "description": "Joined Date ISO8601", + "type": "string" + }, + "anime_stats": { + "description": "Anime Stats", + "properties": { + "days_watched": { + "description": "Number of days spent watching Anime", + "type": "number", + "format": "float" + }, + "mean_score": { + "description": "Mean Score", + "type": "number", + "format": "float" + }, + "watching": { + "description": "Anime Watching", + "type": "integer" + }, + "completed": { + "description": "Anime Completed", + "type": "integer" + }, + "on_hold": { + "description": "Anime On-Hold", + "type": "integer" + }, + "dropped": { + "description": "Anime Dropped", + "type": "integer" + }, + "plan_to_watch": { + "description": "Anime Planned to Watch", + "type": "integer" + }, + "total_entries": { + "description": "Total Anime entries on User list", + "type": "integer" + }, + "rewatched": { + "description": "Anime re-watched", + "type": "integer" + }, + "episodes_watched": { + "description": "Number of Anime Episodes Watched", + "type": "integer" + } + }, + "type": "object" + }, + "manga_stats": { + "description": "Manga Stats", + "properties": { + "days_read": { + "description": "Number of days spent reading Manga", + "type": "number", + "format": "float" + }, + "mean_score": { + "description": "Mean Score", + "type": "number", + "format": "float" + }, + "reading": { + "description": "Manga Reading", + "type": "integer" + }, + "completed": { + "description": "Manga Completed", + "type": "integer" + }, + "on_hold": { + "description": "Manga On-Hold", + "type": "integer" + }, + "dropped": { + "description": "Manga Dropped", + "type": "integer" + }, + "plan_to_read": { + "description": "Manga Planned to Read", + "type": "integer" + }, + "total_entries": { + "description": "Total Manga entries on User list", + "type": "integer" + }, + "reread": { + "description": "Manga re-read", + "type": "integer" + }, + "chapters_read": { + "description": "Number of Manga Chapters Read", + "type": "integer" + }, + "volumes_read": { + "description": "Number of Manga Volumes Read", + "type": "integer" + } + }, + "type": "object" + }, + "favorites": { + "description": "Favorite entries", + "properties": { + "anime": { + "description": "Favorite Anime", + "type": "array", + "items": { + "$ref": "#/components/schemas/entry_meta" + } + }, + "manga": { + "description": "Favorite Manga", + "type": "array", + "items": { + "$ref": "#/components/schemas/entry_meta" + } + }, + "characters": { + "description": "Favorite Characters", + "type": "array", + "items": { + "$ref": "#/components/schemas/entry_meta" + } + }, + "people": { + "description": "Favorite People", + "type": "array", + "items": { + "$ref": "#/components/schemas/entry_meta" + } + } + }, + "type": "object" + }, + "about": { + "description": "User About. NOTE: About information is customizable by users through BBCode on MyAnimeList. This means users can add multimedia content, different text sizes, etc. Due to this freeform, Jikan returns parsed HTML. Validate on your end!", + "type": "string" + } + }, + "type": "object" + } + } + }, + "type": "object" + }, + "user statistics": { + "properties": { + "data": { + "properties": { + "anime": { + "description": "Anime Statistics", + "properties": { + "days_watched": { + "description": "Number of days spent watching Anime", + "type": "number", + "format": "float" + }, + "mean_score": { + "description": "Mean Score", + "type": "number", + "format": "float" + }, + "watching": { + "description": "Anime Watching", + "type": "integer" + }, + "completed": { + "description": "Anime Completed", + "type": "integer" + }, + "on_hold": { + "description": "Anime On-Hold", + "type": "integer" + }, + "dropped": { + "description": "Anime Dropped", + "type": "integer" + }, + "plan_to_watch": { + "description": "Anime Planned to Watch", + "type": "integer" + }, + "total_entries": { + "description": "Total Anime entries on User list", + "type": "integer" + }, + "rewatched": { + "description": "Anime re-watched", + "type": "integer" + }, + "episodes_watched": { + "description": "Number of Anime Episodes Watched", + "type": "integer" + } + }, + "type": "object" + }, + "manga": { + "description": "Manga Statistics", + "properties": { + "days_read": { + "description": "Number of days spent reading Manga", + "type": "number", + "format": "float" + }, + "mean_score": { + "description": "Mean Score", + "type": "number", + "format": "float" + }, + "reading": { + "description": "Manga Reading", + "type": "integer" + }, + "completed": { + "description": "Manga Completed", + "type": "integer" + }, + "on_hold": { + "description": "Manga On-Hold", + "type": "integer" + }, + "dropped": { + "description": "Manga Dropped", + "type": "integer" + }, + "plan_to_read": { + "description": "Manga Planned to Read", + "type": "integer" + }, + "total_entries": { + "description": "Total Manga entries on User list", + "type": "integer" + }, + "reread": { + "description": "Manga re-read", + "type": "integer" + }, + "chapters_read": { + "description": "Number of Manga Chapters Read", + "type": "integer" + }, + "volumes_read": { + "description": "Number of Manga Volumes Read", + "type": "integer" + } + }, + "type": "object" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "recommendations": { + "description": "Recommendations", + "allOf": [ + { + "properties": { + "data": { + "type": "array", + "items": { + "properties": { + "mal_id": { + "description": "MAL IDs of recommendations is both of the MAL ID's with a `-` delimiter", + "type": "String" + }, + "entry": { + "description": "Array of 2 entries that are being recommended to each other", + "type": "array", + "items": { + "type": "object", + "anyOf": [ + { + "$ref": "#/components/schemas/anime meta" + }, + { + "$ref": "#/components/schemas/manga meta" + } + ] + } + }, + "content": { + "description": "Recommendation context provided by the user", + "type": "string" + }, + "user": { + "$ref": "#/components/schemas/user by id" + } + }, + "type": "object" + } + } + }, + "type": "object" + }, + { + "$ref": "#/components/schemas/pagination" + } + ] + }, + "entry recommendations": { + "description": "Entry Recommendations Resource", + "properties": { + "data": { + "type": "array", + "items": { + "properties": { + "entry": { + "description": "Array of 2 entries that are being recommended to each other", + "type": "array", + "items": { + "type": "object", + "anyOf": [ + { + "$ref": "#/components/schemas/anime meta" + }, + { + "$ref": "#/components/schemas/manga meta" + } + ] + } + }, + "url": { + "description": "Recommendation MyAnimeList URL", + "type": "string" + }, + "votes": { + "description": "Number of users who have recommended this entry", + "type": "integer" + } + }, + "type": "object" + } + } + }, + "type": "object" + }, + "manga review": { + "properties": { + "mal_id": { + "description": "MyAnimeList ID", + "type": "integer" + }, + "url": { + "description": "MyAnimeList URL", + "type": "string" + }, + "type": { + "description": "Entry Type", + "type": "string" + }, + "votes": { + "description": "Number of user votes on the Review", + "type": "integer" + }, + "date": { + "description": "Review created date ISO8601", + "type": "string" + }, + "chapters_read": { + "description": "Number of chapters read by the reviewer", + "type": "integer" + }, + "review": { + "description": "Review content", + "type": "string" + }, + "scores": { + "description": "Review Scores breakdown", + "properties": { + "overall": { + "description": "Overall Score", + "type": "integer" + }, + "story": { + "description": "Story Score", + "type": "integer" + }, + "art": { + "description": "Art Score", + "type": "integer" + }, + "character": { + "description": "Character Score", + "type": "integer" + }, + "enjoyment": { + "description": "Enjoyment Score", + "type": "integer" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "anime review": { + "properties": { + "mal_id": { + "description": "MyAnimeList ID", + "type": "integer" + }, + "url": { + "description": "MyAnimeList URL", + "type": "string" + }, + "type": { + "description": "Entry Type", + "type": "string" + }, + "votes": { + "description": "Number of user votes on the Review", + "type": "integer" + }, + "date": { + "description": "Review created date ISO8601", + "type": "string" + }, + "review": { + "description": "Review content", + "type": "string" + }, + "episodes_watched": { + "description": "Number of episodes watched", + "type": "integer" + }, + "scores": { + "description": "Review Scores breakdown", + "properties": { + "overall": { + "description": "Overall Score", + "type": "integer" + }, + "story": { + "description": "Story Score", + "type": "integer" + }, + "animation": { + "description": "Animation Score", + "type": "integer" + }, + "sound": { + "description": "Sound Score", + "type": "integer" + }, + "character": { + "description": "Character Score", + "type": "integer" + }, + "enjoyment": { + "description": "Enjoyment Score", + "type": "integer" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "anime reviews": { + "description": "Anime Reviews Resource", + "allOf": [ + { + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "allOf": [ + { + "properties": { + "user": { + "$ref": "#/components/schemas/user meta" + } + }, + "type": "object" + }, + { + "$ref": "#/components/schemas/anime review" + } + ] + } + } + }, + "type": "object" + }, + { + "$ref": "#/components/schemas/pagination" + } + ] + }, + "manga reviews": { + "description": "Manga Reviews Resource", + "allOf": [ + { + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "allOf": [ + { + "properties": { + "user": { + "$ref": "#/components/schemas/user meta" + } + }, + "type": "object" + }, + { + "$ref": "#/components/schemas/manga review" + } + ] + } + } + }, + "type": "object" + }, + { + "$ref": "#/components/schemas/pagination" + } + ] + }, + "anime userupdates": { + "description": "Anime User Updates Resource", + "allOf": [ + { + "properties": { + "data": { + "type": "array", + "items": { + "properties": { + "user": { + "$ref": "#/components/schemas/user meta" + }, + "score": { + "description": "User Score", + "type": "integer" + }, + "status": { + "description": "User list status", + "type": "string" + }, + "episodes_seen": { + "description": "Number of episodes seen", + "type": "integer" + }, + "episodes_total": { + "description": "Total number of episodes", + "type": "integer" + }, + "date": { + "description": "Last updated date ISO8601", + "type": "string" + } + }, + "type": "object" + } + } + }, + "type": "object" + }, + { + "$ref": "#/components/schemas/pagination" + } + ] + }, + "manga userupdates": { + "description": "Manga User Updates Resource", + "allOf": [ + { + "$ref": "#/components/schemas/pagination" + }, + { + "property": "data", + "type": "array", + "items": { + "properties": { + "user": { + "$ref": "#/components/schemas/user meta" + }, + "score": { + "description": "User Score", + "type": "integer" + }, + "status": { + "description": "User list status", + "type": "string" + }, + "volumes_read": { + "description": "Number of volumes read", + "type": "integer" + }, + "volumes_total": { + "description": "Total number of volumes", + "type": "integer" + }, + "chapters_read": { + "description": "Number of chapters read", + "type": "integer" + }, + "chapters_total": { + "description": "Total number of chapters", + "type": "integer" + }, + "date": { + "description": "Last updated date ISO8601", + "type": "string" + } + }, + "type": "object" + } + } + ] + } + }, + "parameters": { + "page": { + "name": "page", + "in": "query", + "schema": { + "type": "integer" + } + }, + "limit": { + "name": "limit", + "in": "query", + "schema": { + "type": "integer" + } + } + } + }, + "externalDocs": { + "description": "About", + "url": "https://jikan.moe" + } +} \ No newline at end of file diff --git a/storage/app/.gitignore b/storage/app/.gitignore index e69de29..0407052 100755 --- a/storage/app/.gitignore +++ b/storage/app/.gitignore @@ -0,0 +1,3 @@ +!.gitignore +failovers.json +source_failover.lock \ No newline at end of file diff --git a/storage/app/blacklist.json b/storage/app/blacklist.json deleted file mode 100755 index 0637a08..0000000 --- a/storage/app/blacklist.json +++ /dev/null @@ -1 +0,0 @@ -[] \ No newline at end of file diff --git a/storage/app/metadata.v3/Jikan.Model.Anime.Anime.yml b/storage/app/metadata.v4/Jikan.Model.Anime.Anime.yml old mode 100755 new mode 100644 similarity index 100% rename from storage/app/metadata.v3/Jikan.Model.Anime.Anime.yml rename to storage/app/metadata.v4/Jikan.Model.Anime.Anime.yml diff --git a/storage/app/metadata.v3/Jikan.Model.Anime.AnimeCharactersAndStaff.yml b/storage/app/metadata.v4/Jikan.Model.Anime.AnimeCharactersAndStaff.yml old mode 100755 new mode 100644 similarity index 100% rename from storage/app/metadata.v3/Jikan.Model.Anime.AnimeCharactersAndStaff.yml rename to storage/app/metadata.v4/Jikan.Model.Anime.AnimeCharactersAndStaff.yml diff --git a/storage/app/metadata.v3/Jikan.Model.Anime.EpisodeListItem.yml b/storage/app/metadata.v4/Jikan.Model.Anime.EpisodeListItem.yml old mode 100755 new mode 100644 similarity index 100% rename from storage/app/metadata.v3/Jikan.Model.Anime.EpisodeListItem.yml rename to storage/app/metadata.v4/Jikan.Model.Anime.EpisodeListItem.yml diff --git a/storage/app/metadata.v3/Jikan.Model.Anime.Episodes.yml b/storage/app/metadata.v4/Jikan.Model.Anime.Episodes.yml old mode 100755 new mode 100644 similarity index 100% rename from storage/app/metadata.v3/Jikan.Model.Anime.Episodes.yml rename to storage/app/metadata.v4/Jikan.Model.Anime.Episodes.yml diff --git a/storage/app/metadata.v3/Jikan.Model.Anime.PromoListItem.yml b/storage/app/metadata.v4/Jikan.Model.Anime.PromoListItem.yml old mode 100755 new mode 100644 similarity index 100% rename from storage/app/metadata.v3/Jikan.Model.Anime.PromoListItem.yml rename to storage/app/metadata.v4/Jikan.Model.Anime.PromoListItem.yml diff --git a/storage/app/metadata.v3/Jikan.Model.Anime.StaffListItem.yml b/storage/app/metadata.v4/Jikan.Model.Anime.StaffListItem.yml old mode 100755 new mode 100644 similarity index 100% rename from storage/app/metadata.v3/Jikan.Model.Anime.StaffListItem.yml rename to storage/app/metadata.v4/Jikan.Model.Anime.StaffListItem.yml diff --git a/storage/app/metadata.v3/Jikan.Model.Anime.StreamEpisodeListItem.yml b/storage/app/metadata.v4/Jikan.Model.Anime.StreamEpisodeListItem.yml old mode 100755 new mode 100644 similarity index 100% rename from storage/app/metadata.v3/Jikan.Model.Anime.StreamEpisodeListItem.yml rename to storage/app/metadata.v4/Jikan.Model.Anime.StreamEpisodeListItem.yml diff --git a/storage/app/metadata.v3/Jikan.Model.Character.Animeography.yml b/storage/app/metadata.v4/Jikan.Model.Character.Animeography.yml old mode 100755 new mode 100644 similarity index 100% rename from storage/app/metadata.v3/Jikan.Model.Character.Animeography.yml rename to storage/app/metadata.v4/Jikan.Model.Character.Animeography.yml diff --git a/storage/app/metadata.v3/Jikan.Model.Character.Character.yml b/storage/app/metadata.v4/Jikan.Model.Character.Character.yml old mode 100755 new mode 100644 similarity index 100% rename from storage/app/metadata.v3/Jikan.Model.Character.Character.yml rename to storage/app/metadata.v4/Jikan.Model.Character.Character.yml diff --git a/storage/app/metadata.v3/Jikan.Model.Character.CharacterListItem.yml b/storage/app/metadata.v4/Jikan.Model.Character.CharacterListItem.yml old mode 100755 new mode 100644 similarity index 100% rename from storage/app/metadata.v3/Jikan.Model.Character.CharacterListItem.yml rename to storage/app/metadata.v4/Jikan.Model.Character.CharacterListItem.yml diff --git a/storage/app/metadata.v3/Jikan.Model.Character.Mangaography.yml b/storage/app/metadata.v4/Jikan.Model.Character.Mangaography.yml old mode 100755 new mode 100644 similarity index 100% rename from storage/app/metadata.v3/Jikan.Model.Character.Mangaography.yml rename to storage/app/metadata.v4/Jikan.Model.Character.Mangaography.yml diff --git a/storage/app/metadata.v3/Jikan.Model.Character.VoiceActor.yml b/storage/app/metadata.v4/Jikan.Model.Character.VoiceActor.yml old mode 100755 new mode 100644 similarity index 100% rename from storage/app/metadata.v3/Jikan.Model.Character.VoiceActor.yml rename to storage/app/metadata.v4/Jikan.Model.Character.VoiceActor.yml diff --git a/storage/app/metadata.v3/Jikan.Model.Common.AnimeCard.yml b/storage/app/metadata.v4/Jikan.Model.Common.AnimeCard.yml old mode 100755 new mode 100644 similarity index 100% rename from storage/app/metadata.v3/Jikan.Model.Common.AnimeCard.yml rename to storage/app/metadata.v4/Jikan.Model.Common.AnimeCard.yml diff --git a/storage/app/metadata.v3/Jikan.Model.Common.MangaCard.yml b/storage/app/metadata.v4/Jikan.Model.Common.MangaCard.yml old mode 100755 new mode 100644 similarity index 100% rename from storage/app/metadata.v3/Jikan.Model.Common.MangaCard.yml rename to storage/app/metadata.v4/Jikan.Model.Common.MangaCard.yml diff --git a/storage/app/metadata.v3/Jikan.Model.Common.Picture.yml b/storage/app/metadata.v4/Jikan.Model.Common.Picture.yml old mode 100755 new mode 100644 similarity index 100% rename from storage/app/metadata.v3/Jikan.Model.Common.Picture.yml rename to storage/app/metadata.v4/Jikan.Model.Common.Picture.yml diff --git a/storage/app/metadata.v3/Jikan.Model.Forum.ForumTopic.yml b/storage/app/metadata.v4/Jikan.Model.Forum.ForumTopic.yml old mode 100755 new mode 100644 similarity index 100% rename from storage/app/metadata.v3/Jikan.Model.Forum.ForumTopic.yml rename to storage/app/metadata.v4/Jikan.Model.Forum.ForumTopic.yml diff --git a/storage/app/metadata.v4/Jikan.Model.Genre.AnimeGenre.yml b/storage/app/metadata.v4/Jikan.Model.Genre.AnimeGenre.yml new file mode 100644 index 0000000..56a82de --- /dev/null +++ b/storage/app/metadata.v4/Jikan.Model.Genre.AnimeGenre.yml @@ -0,0 +1,5 @@ +Jikan\Model\Genre\AnimeGenre: + exclusion_policy: NONE + properties: + malUrl: + serialized_name: meta \ No newline at end of file diff --git a/storage/app/metadata.v4/Jikan.Model.Genre.MangaGenre.yml b/storage/app/metadata.v4/Jikan.Model.Genre.MangaGenre.yml new file mode 100644 index 0000000..2cf6152 --- /dev/null +++ b/storage/app/metadata.v4/Jikan.Model.Genre.MangaGenre.yml @@ -0,0 +1,5 @@ +Jikan\Model\Genre\MangaGenre: + exclusion_policy: NONE + properties: + malUrl: + serialized_name: meta \ No newline at end of file diff --git a/storage/app/metadata.v3/Jikan.Model.Magazine.Magazine.yml b/storage/app/metadata.v4/Jikan.Model.Magazine.Magazine.yml old mode 100755 new mode 100644 similarity index 100% rename from storage/app/metadata.v3/Jikan.Model.Magazine.Magazine.yml rename to storage/app/metadata.v4/Jikan.Model.Magazine.Magazine.yml diff --git a/storage/app/metadata.v3/Jikan.Model.Manga.CharacterListItem.yml b/storage/app/metadata.v4/Jikan.Model.Manga.CharacterListItem.yml old mode 100755 new mode 100644 similarity index 100% rename from storage/app/metadata.v3/Jikan.Model.Manga.CharacterListItem.yml rename to storage/app/metadata.v4/Jikan.Model.Manga.CharacterListItem.yml diff --git a/storage/app/metadata.v3/Jikan.Model.Manga.Manga.yml b/storage/app/metadata.v4/Jikan.Model.Manga.Manga.yml old mode 100755 new mode 100644 similarity index 100% rename from storage/app/metadata.v3/Jikan.Model.Manga.Manga.yml rename to storage/app/metadata.v4/Jikan.Model.Manga.Manga.yml diff --git a/storage/app/metadata.v3/Jikan.Model.News.NewsListItem.yml b/storage/app/metadata.v4/Jikan.Model.News.NewsListItem.yml old mode 100755 new mode 100644 similarity index 100% rename from storage/app/metadata.v3/Jikan.Model.News.NewsListItem.yml rename to storage/app/metadata.v4/Jikan.Model.News.NewsListItem.yml diff --git a/storage/app/metadata.v3/Jikan.Model.Person.AnimeStaffPosition.yml b/storage/app/metadata.v4/Jikan.Model.Person.AnimeStaffPosition.yml old mode 100755 new mode 100644 similarity index 100% rename from storage/app/metadata.v3/Jikan.Model.Person.AnimeStaffPosition.yml rename to storage/app/metadata.v4/Jikan.Model.Person.AnimeStaffPosition.yml diff --git a/storage/app/metadata.v3/Jikan.Model.Person.Person.yml b/storage/app/metadata.v4/Jikan.Model.Person.Person.yml old mode 100755 new mode 100644 similarity index 100% rename from storage/app/metadata.v3/Jikan.Model.Person.Person.yml rename to storage/app/metadata.v4/Jikan.Model.Person.Person.yml diff --git a/storage/app/metadata.v3/Jikan.Model.Person.PublishedManga.yml b/storage/app/metadata.v4/Jikan.Model.Person.PublishedManga.yml old mode 100755 new mode 100644 similarity index 100% rename from storage/app/metadata.v3/Jikan.Model.Person.PublishedManga.yml rename to storage/app/metadata.v4/Jikan.Model.Person.PublishedManga.yml diff --git a/storage/app/metadata.v3/Jikan.Model.Person.VoiceActingRole.yml b/storage/app/metadata.v4/Jikan.Model.Person.VoiceActingRole.yml old mode 100755 new mode 100644 similarity index 100% rename from storage/app/metadata.v3/Jikan.Model.Person.VoiceActingRole.yml rename to storage/app/metadata.v4/Jikan.Model.Person.VoiceActingRole.yml diff --git a/storage/app/metadata.v3/Jikan.Model.Producer.Producer.yml b/storage/app/metadata.v4/Jikan.Model.Producer.Producer.yml old mode 100755 new mode 100644 similarity index 100% rename from storage/app/metadata.v3/Jikan.Model.Producer.Producer.yml rename to storage/app/metadata.v4/Jikan.Model.Producer.Producer.yml diff --git a/storage/app/metadata.v3/Jikan.Model.Schedule.Schedule.yml b/storage/app/metadata.v4/Jikan.Model.Schedule.Schedule.yml old mode 100755 new mode 100644 similarity index 100% rename from storage/app/metadata.v3/Jikan.Model.Schedule.Schedule.yml rename to storage/app/metadata.v4/Jikan.Model.Schedule.Schedule.yml diff --git a/storage/app/metadata.v3/Jikan.Model.Search.AnimeSearchListItem.yml b/storage/app/metadata.v4/Jikan.Model.Search.AnimeSearchListItem.yml old mode 100755 new mode 100644 similarity index 100% rename from storage/app/metadata.v3/Jikan.Model.Search.AnimeSearchListItem.yml rename to storage/app/metadata.v4/Jikan.Model.Search.AnimeSearchListItem.yml diff --git a/storage/app/metadata.v3/Jikan.Model.Search.CharacterSearchListItem.yml b/storage/app/metadata.v4/Jikan.Model.Search.CharacterSearchListItem.yml old mode 100755 new mode 100644 similarity index 100% rename from storage/app/metadata.v3/Jikan.Model.Search.CharacterSearchListItem.yml rename to storage/app/metadata.v4/Jikan.Model.Search.CharacterSearchListItem.yml diff --git a/storage/app/metadata.v3/Jikan.Model.Search.MangaSearchListItem.yml b/storage/app/metadata.v4/Jikan.Model.Search.MangaSearchListItem.yml old mode 100755 new mode 100644 similarity index 100% rename from storage/app/metadata.v3/Jikan.Model.Search.MangaSearchListItem.yml rename to storage/app/metadata.v4/Jikan.Model.Search.MangaSearchListItem.yml diff --git a/storage/app/metadata.v3/Jikan.Model.Search.PersonSearchListItem.yml b/storage/app/metadata.v4/Jikan.Model.Search.PersonSearchListItem.yml old mode 100755 new mode 100644 similarity index 100% rename from storage/app/metadata.v3/Jikan.Model.Search.PersonSearchListItem.yml rename to storage/app/metadata.v4/Jikan.Model.Search.PersonSearchListItem.yml diff --git a/storage/app/metadata.v3/Jikan.Model.Top.TopAnime.yml b/storage/app/metadata.v4/Jikan.Model.Top.TopAnime.yml old mode 100755 new mode 100644 similarity index 100% rename from storage/app/metadata.v3/Jikan.Model.Top.TopAnime.yml rename to storage/app/metadata.v4/Jikan.Model.Top.TopAnime.yml diff --git a/storage/app/metadata.v3/Jikan.Model.Top.TopCharacter.yml b/storage/app/metadata.v4/Jikan.Model.Top.TopCharacter.yml old mode 100755 new mode 100644 similarity index 100% rename from storage/app/metadata.v3/Jikan.Model.Top.TopCharacter.yml rename to storage/app/metadata.v4/Jikan.Model.Top.TopCharacter.yml diff --git a/storage/app/metadata.v3/Jikan.Model.Top.TopManga.yml b/storage/app/metadata.v4/Jikan.Model.Top.TopManga.yml old mode 100755 new mode 100644 similarity index 100% rename from storage/app/metadata.v3/Jikan.Model.Top.TopManga.yml rename to storage/app/metadata.v4/Jikan.Model.Top.TopManga.yml diff --git a/storage/app/metadata.v3/Jikan.Model.Top.TopPerson.yml b/storage/app/metadata.v4/Jikan.Model.Top.TopPerson.yml old mode 100755 new mode 100644 similarity index 100% rename from storage/app/metadata.v3/Jikan.Model.Top.TopPerson.yml rename to storage/app/metadata.v4/Jikan.Model.Top.TopPerson.yml diff --git a/storage/app/metadata.v3/Jikan.Model.User.History.yml b/storage/app/metadata.v4/Jikan.Model.User.History.yml old mode 100755 new mode 100644 similarity index 73% rename from storage/app/metadata.v3/Jikan.Model.User.History.yml rename to storage/app/metadata.v4/Jikan.Model.User.History.yml index 9a3a67e..a6d220f --- a/storage/app/metadata.v3/Jikan.Model.User.History.yml +++ b/storage/app/metadata.v4/Jikan.Model.User.History.yml @@ -2,4 +2,4 @@ Jikan\Model\User\History: exclusion_policy: NONE properties: malUrl: - serialized_name: meta \ No newline at end of file + serialized_name: entry \ No newline at end of file diff --git a/storage/logs/.gitignore b/storage/logs/.gitignore index d6b7ef3..29b3e9b 100755 --- a/storage/logs/.gitignore +++ b/storage/logs/.gitignore @@ -1,2 +1,5 @@ * !.gitignore +source-health-monitor.log +worker.error.log +worker.log \ No newline at end of file diff --git a/tests/Http/Controllers/AnimeControllerTest.php b/tests/Http/Controllers/AnimeControllerTest.php deleted file mode 100755 index 7dbb72f..0000000 --- a/tests/Http/Controllers/AnimeControllerTest.php +++ /dev/null @@ -1,364 +0,0 @@ -get('/v3/anime/1') - ->seeStatusCode(200) - ->seeJsonStructure([ - 'mal_id', - 'url', - 'image_url', - 'trailer_url', - 'title', - 'title_english', - 'title_japanese', - 'title_synonyms', - 'type', - 'source', - 'episodes', - 'status', - 'airing', - 'aired' => [ - 'from', - 'to', - 'prop' => [ - 'from' => [ - 'day', - 'month', - 'year' - ], - 'to' => [ - 'day', - 'month', - 'year' - ] - ], - 'string' - ], - 'duration', - 'rating', - 'score', - 'scored_by', - 'rank', - 'popularity', - 'members', - 'favorites', - 'synopsis', - 'background', - 'premiered', - 'broadcast', - 'related' => [ - 'Adaptation' => [ - [ - 'mal_id', - 'type', - 'name', - 'url' - ] - ], - 'Side story' => [ - [ - 'mal_id', - 'type', - 'name', - 'url' - ] - ], - // ... - ], - 'producers' => [ - [ - 'mal_id', - 'type', - 'name', - 'url' - ] - ], - 'licensors' => [ - [ - 'mal_id', - 'type', - 'name', - 'url' - ] - ], - 'studios' => [ - [ - 'mal_id', - 'type', - 'name', - 'url' - ] - ], - 'genres' => [ - [ - 'mal_id', - 'type', - 'name', - 'url' - ] - ], - 'opening_themes', - 'ending_themes' - ]); - } - - public function testCharactersStaff() - { - $this->get('/v3/anime/1/characters_staff') - ->seeStatusCode(200) - ->seeJsonStructure([ - 'characters' => [ - [ - 'mal_id', - 'url', - 'image_url', - 'name', - 'voice_actors' => [ - [ - 'mal_id', - 'name', - 'image_url', - 'language' - ] - ] - ] - ], - 'staff' => [ - [ - 'mal_id', - 'url', - 'name', - 'image_url', - 'positions' - ] - ], - ]); - } - - public function testEpisodes() - { - $this->get('/v3/anime/1/episodes') - ->seeStatusCode(200) - ->seeJsonStructure([ - 'episodes_last_page', - 'episodes' => [ - [ - 'episode_id', - 'title', - 'title_japanese', - 'title_romanji', - 'aired', - 'filler', - 'recap', - 'video_url', - 'forum_url' - ] - ] - ]); - - $this->get('/v3/anime/1/episodes/2') - ->seeStatusCode(200) - ->seeJson([ - 'episodes' => [] - ]); - } - - public function testNews() - { - $this->get('/v3/anime/1/news') - ->seeStatusCode(200) - ->seeJsonStructure([ - 'articles' => [ - [ - 'url', - 'title', - 'date', - 'author_name', - 'author_url', - 'forum_url', - 'image_url', - 'comments', - 'intro' - ] - ] - ]); - } - - public function testPictures() - { - $this->get('/v3/anime/1/pictures') - ->seeStatusCode(200) - ->seeJsonStructure([ - 'pictures' => [ - [ - 'large', - 'small', - ] - ] - ]); - } - - public function testVideos() - { - $this->get('/v3/anime/1/videos') - ->seeStatusCode(200) - ->seeJsonStructure([ - 'promo' => [ - [ - 'title', - 'image_url', - 'video_url', - ] - ], - 'episodes' => [ - [ - 'title', - 'episode', - 'url', - 'image_url', - ] - ] - ]); - } - - public function testStats() - { - $this->get('/v3/anime/1/stats') - ->seeStatusCode(200) - ->seeJsonStructure([ - 'watching', - 'completed', - 'on_hold', - 'dropped', - 'plan_to_watch', - 'total', - 'scores' => [ - 1 => [ - 'votes', - 'percentage' - ] - ] - ]); - } - - public function testForum() - { - $this->get('/v3/anime/1/forum') - ->seeStatusCode(200) - ->seeJsonStructure([ - 'topics' => [ - [ - 'topic_id', - 'url', - 'title', - 'date_posted', - 'author_name', - 'author_url', - 'replies', - 'last_post' => [ - 'url', - 'author_name', - 'author_url', - 'date_posted' - ] - ] - ] - ]); - } - - public function testMoreInfo() - { - $this->get('/v3/anime/1/moreinfo') - ->seeStatusCode(200) - ->seeJsonStructure([ - 'moreinfo' - ]); - } - - public function testReviews() - { - $this->get('/v3/anime/1/reviews') - ->seeStatusCode(200) - ->seeJsonStructure([ - 'reviews' => [ - [ - 'mal_id', - 'url', - 'helpful_count', - 'date', - 'reviewer' => [ - 'url', - 'image_url', - 'username', - 'episodes_seen', - 'scores' => [ - 'overall', - 'story', - 'animation', - 'sound', - 'character', - 'enjoyment' - ], - ], - 'content' - ] - ] - ]); - - $this->get('/v3/anime/1/reviews/100') - ->seeStatusCode(200) - ->seeJsonStructure([ - 'reviews' => [] - ]); - } - - public function testRecommendations() - { - $this->get('/v3/anime/1/recommendations') - ->seeStatusCode(200) - ->seeJsonStructure([ - 'recommendations' => [ - [ - 'mal_id', - 'url', - 'image_url', - 'recommendation_url', - 'title', - 'recommendation_count' - ] - ] - ]); - } - - public function testUserUpdates() - { - $this->get('/v3/anime/1/userupdates') - ->seeStatusCode(200) - ->seeJsonStructure([ - 'users' => [ - [ - 'username', - 'url', - 'image_url', - 'score', - 'status', - 'episodes_seen', - 'episodes_total', - 'date' - ] - ] - ]); - - $this->get('/v3/anime/1/userupdates/1000') - ->seeStatusCode(404); - } - - public function test404() - { - $this->get('/v3/anime/2') - ->seeStatusCode(404); - } -} diff --git a/tests/Http/Controllers/CharacterControllerTest.php b/tests/Http/Controllers/CharacterControllerTest.php deleted file mode 100755 index dac6e06..0000000 --- a/tests/Http/Controllers/CharacterControllerTest.php +++ /dev/null @@ -1,67 +0,0 @@ -get('/v3/character/1') - ->seeStatusCode(200) - ->seeJsonStructure([ - 'mal_id', - 'url', - 'image_url', - 'name', - 'name_kanji', - 'nicknames', - 'about', - 'member_favorites', - 'animeography' => [ - [ - 'mal_id', - 'name', - 'url', - 'image_url', - 'role' - ] - ], - 'mangaography' => [ - [ - 'mal_id', - 'name', - 'url', - 'image_url', - 'role' - ] - ], - 'voice_actors' => [ - [ - 'mal_id', - 'name', - 'url', - 'image_url', - 'language' - ] - ] - ]); - } - - public function testPictures() - { - $this->get('/v3/character/1/pictures') - ->seeStatusCode(200) - ->seeJsonStructure([ - 'pictures' => [ - [ - 'large', - 'small', - ] - ] - ]); - } - - public function test404() - { - $this->get('/v3/character/1000000') - ->seeStatusCode(404); - } -} diff --git a/tests/Http/Controllers/GenreControllerTest.php b/tests/Http/Controllers/GenreControllerTest.php deleted file mode 100755 index 8931db6..0000000 --- a/tests/Http/Controllers/GenreControllerTest.php +++ /dev/null @@ -1,112 +0,0 @@ -get('/v3/genre/anime/1') - ->seeStatusCode(200) - ->seeJsonStructure([ - 'mal_url' => [ - 'mal_id', - 'type', - 'name', - 'url' - ], - 'item_count', - 'anime' => [ - [ - 'mal_id', - 'url', - 'title', - 'image_url', - 'synopsis', - 'type', - 'airing_start', - 'episodes', - 'members', - 'genres' => [ - [ - 'mal_id', - 'type', - 'name', - 'url' - ] - ], - 'source', - 'producers' => [ - [ - 'mal_id', - 'type', - 'name', - 'url' - ] - ], - 'score', - 'licensors', - 'r18', - 'kids' - ] - ] - ]); - } - - public function testMangaGenre() - { - $this->get('/v3/genre/manga/1') - ->seeStatusCode(200) - ->seeJsonStructure([ - 'mal_url' => [ - 'mal_id', - 'type', - 'name', - 'url' - ], - 'item_count', - 'manga' => [ - [ - 'mal_id', - 'url', - 'title', - 'image_url', - 'synopsis', - 'type', - 'publishing_start', - 'volumes', - 'members', - 'genres' => [ - [ - 'mal_id', - 'type', - 'name', - 'url' - ] - ], - 'authors' => [ - [ - 'mal_id', - 'type', - 'name', - 'url' - ] - ], - 'score', - 'serialization', -// 'r18', todo ? - ] - ] - ]); - } - - public function test404() - { - $this->get('/v3/genre/anime/1/1000') - ->seeStatusCode(404); - $this->get('/v3/genre/manga/1/1000') - ->seeStatusCode(404); - $this->get('/v3/genre/anime/100') - ->seeStatusCode(404); - $this->get('/v3/genre/manga/100') - ->seeStatusCode(404); - } -} diff --git a/tests/Http/Controllers/MagazineControllerTest.php b/tests/Http/Controllers/MagazineControllerTest.php deleted file mode 100755 index 79bac7c..0000000 --- a/tests/Http/Controllers/MagazineControllerTest.php +++ /dev/null @@ -1,62 +0,0 @@ -get('/v3/magazine/1') - ->seeStatusCode(200) - ->seeJsonStructure([ - 'meta' => [ - 'mal_id', - 'type', - 'name', - 'url' - ], - 'manga' => [ - [ - 'mal_id', - 'url', - 'title', - 'image_url', - 'synopsis', - 'type', - 'publishing_start', - 'volumes', - 'members', - 'genres' => [ - [ - 'mal_id', - 'type', - 'name', - 'url' - ] - ], - 'authors' => [ - [ - 'mal_id', - 'type', - 'name', - 'url' - ] - ], - 'score', - 'serialization', -// 'r18', todo ? - ] - ] - ]); - } - - public function test404() - { - $this->get('/v3/magazine/1/1000') - ->seeStatusCode(404); - $this->get('/v3/magazine/1/1000') - ->seeStatusCode(404); - $this->get('/v3/magazine/100000') - ->seeStatusCode(404); - $this->get('/v3/magazine/100000') - ->seeStatusCode(404); - } -} diff --git a/tests/Http/Controllers/PersonControllerTest.php b/tests/Http/Controllers/PersonControllerTest.php deleted file mode 100755 index 55969e0..0000000 --- a/tests/Http/Controllers/PersonControllerTest.php +++ /dev/null @@ -1,72 +0,0 @@ -get('/v3/person/1') - ->seeStatusCode(200) - ->seeJsonStructure([ - 'mal_id', - 'url', - 'image_url', - 'website_url', - 'name', - 'given_name', - 'family_name', - 'alternate_names', - 'birthday', - 'about', - 'member_favorites', - 'voice_acting_roles' => [ - [ - 'role', - 'anime' => [ - 'mal_id', - 'url', - 'image_url', - 'name' - ], - 'character' => [ - 'mal_id', - 'url', - 'image_url', - 'name' - ] - ] - ], - 'anime_staff_positions' => [ - [ - 'position', - 'anime' => [ - 'mal_id', - 'url', - 'image_url', - 'name' - ], - ] - ], - 'published_manga' => [] - ]); - } - - public function testPictures() - { - $this->get('/v3/person/1/pictures') - ->seeStatusCode(200) - ->seeJsonStructure([ - 'pictures' => [ - [ - 'large', - 'small', - ] - ] - ]); - } - - public function test404() - { - $this->get('/v3/person/1000000') - ->seeStatusCode(404); - } -} diff --git a/tests/Http/Controllers/ProducerControllerTest.php b/tests/Http/Controllers/ProducerControllerTest.php deleted file mode 100755 index ea7836f..0000000 --- a/tests/Http/Controllers/ProducerControllerTest.php +++ /dev/null @@ -1,64 +0,0 @@ -get('/v3/producer/1') - ->seeStatusCode(200) - ->seeJsonStructure([ - 'meta' => [ - 'mal_id', - 'type', - 'name', - 'url' - ], - 'anime' => [ - [ - 'mal_id', - 'url', - 'title', - 'image_url', - 'synopsis', - 'type', - 'airing_start', - 'episodes', - 'members', - 'genres' => [ - [ - 'mal_id', - 'type', - 'name', - 'url' - ] - ], - 'source', - 'producers' => [ - [ - 'mal_id', - 'type', - 'name', - 'url' - ] - ], - 'score', - 'licensors', - 'r18', - 'kids' - ] - ] - ]); - } - - public function test404() - { - $this->get('/v3/producer/1/1000') - ->seeStatusCode(404); - $this->get('/v3/producer/1/1000') - ->seeStatusCode(404); - $this->get('/v3/producer/100000') - ->seeStatusCode(404); - $this->get('/v3/producer/100000') - ->seeStatusCode(404); - } -} diff --git a/tests/Http/Controllers/SeasonControllerTest.php b/tests/Http/Controllers/SeasonControllerTest.php deleted file mode 100755 index d63f2cb..0000000 --- a/tests/Http/Controllers/SeasonControllerTest.php +++ /dev/null @@ -1,49 +0,0 @@ -get('/v3/season') - ->seeStatusCode(200) - ->seeJsonStructure([ - 'season_name', - 'season_year', - 'anime' => [ - [ - 'mal_id', - 'url', - 'title', - 'image_url', - 'synopsis', - 'type', - 'airing_start', - 'episodes', - 'members', - 'genres' => [ - [ - 'mal_id', - 'type', - 'name', - 'url' - ] - ], - 'source', - 'producers' => [ - [ - 'mal_id', - 'type', - 'name', - 'url' - ] - ], - 'score', - 'licensors', - 'r18', - 'kids', - 'continuing' - ] - ] - ]); - } -} diff --git a/tests/Http/Controllers/UserControllerTest.php b/tests/Http/Controllers/UserControllerTest.php deleted file mode 100755 index 5e56a01..0000000 --- a/tests/Http/Controllers/UserControllerTest.php +++ /dev/null @@ -1,171 +0,0 @@ -get('/v3/user/nekomata1037') - ->seeStatusCode(200) - ->seeJsonStructure([ - 'username', - 'url', - 'image_url', - 'last_online', - 'gender', - 'birthday', - 'location', - 'joined', - 'anime_stats' => [ - 'days_watched', - 'mean_score', - 'watching', - 'completed', - 'on_hold', - 'dropped', - 'plan_to_watch', - 'total_entries', - 'rewatched', - 'episodes_watched' - ], - 'manga_stats' => [ - 'days_read', - 'mean_score', - 'reading', - 'completed', - 'on_hold', - 'dropped', - 'plan_to_read', - 'total_entries', - 'reread', - 'chapters_read', - 'volumes_read' - ], - 'favorites' => [ - 'anime' => [ - [ - 'mal_id', - 'url', - 'image_url', - 'name' // todo should be `title` - ] - ], - 'manga' => [], - 'characters' => [ - [ - 'mal_id', - 'url', - 'image_url', - 'name' - ] - ], - 'people' => [ - [ - 'mal_id', - 'url', - 'image_url', - 'name' - ] - ], - ], - 'about' - ]); - } - - public function testUserHistory() - { - $this->get('/v3/user/nekomata1037/history') - ->seeStatusCode(200) - ->seeJsonStructure([ - 'history' => [ - [ - 'meta' => [ - 'mal_id', - 'type', - 'name', - 'url' - ], - 'increment', - 'date' - ] - ] - ]); - } - - public function testUserFriends() - { - $this->get('/v3/user/nekomata1037/friends') - ->seeStatusCode(200) - ->seeJsonStructure([ - 'friends' => [ - [ - 'url', - 'username', - 'image_url', - 'last_online', - 'friends_since' - ] - ] - ]); - } - - public function testUserAnimeList() - { - $this->get('/v3/user/nekomata1037/animelist?order_by=last_updated&sort=descending') - ->seeStatusCode(200) - ->seeJsonStructure([ - 'anime' => [ - [ - 'mal_id', - 'title', - 'video_url', - 'url', - 'image_url', - 'type', - 'watching_status', - 'score', - 'watched_episodes', - 'total_episodes', - 'airing_status', - 'season_name', - 'season_year', - 'has_episode_video', - 'has_promo_video', - 'has_video', - 'is_rewatching', - 'tags', - 'rating', - 'start_date', - 'end_date', - 'watch_start_date', - 'watch_end_date', - 'days', - 'storage', - 'priority', - 'added_to_list', - 'studios' => [ - [ - 'mal_id', - 'type', - 'name', - 'url' - ] - ], - 'licensors' => [ - [ - 'mal_id', - 'type', - 'name', - 'url' - ] - ], - ] - ] - ]); - } - - public function testUserMangaList() - { - $this->get('/v3/user/nekomata1037/mangalist?order_by=last_updated&sort=descending') - ->seeStatusCode(400); - } -} diff --git a/tests/HttpV4/Controllers/AnimeControllerTest.php b/tests/HttpV4/Controllers/AnimeControllerTest.php new file mode 100644 index 0000000..f640b7b --- /dev/null +++ b/tests/HttpV4/Controllers/AnimeControllerTest.php @@ -0,0 +1,542 @@ +get('/v4/anime/1') + ->seeStatusCode(200) + ->seeJsonStructure(['data' => [ + 'mal_id', + 'url', + 'images' => [ + 'jpg' => [ + 'image_url', + 'small_image_url', + 'large_image_url' + ], + 'webp' => [ + 'image_url', + 'small_image_url', + 'large_image_url' + ], + ], + 'trailer' => [ + 'youtube_id', + 'url', + 'embed_url', + 'images' => [ + 'default_image_url', + 'small_image_url', + 'medium_image_url', + 'large_image_url', + 'maximum_image_url', + ] + ], + 'title', + 'title_english', + 'title_japanese', + 'title_synonyms', + 'type', + 'source', + 'episodes', + 'status', + 'airing', + 'aired' => [ + 'from', + 'to', + 'prop' => [ + 'from' => [ + 'day', + 'month', + 'year' + ], + 'to' => [ + 'day', + 'month', + 'year' + ] + ], + 'string' + ], + 'duration', + 'rating', + 'score', + 'scored_by', + 'rank', + 'popularity', + 'members', + 'favorites', + 'synopsis', + 'background', + 'season', + 'year', + 'broadcast' => [ + 'day', + 'time', + 'timezone', + 'string' + ], + 'producers' => [ + [ + 'mal_id', + 'type', + 'name', + 'url' + ] + ], + 'licensors' => [ + [ + 'mal_id', + 'type', + 'name', + 'url' + ] + ], + 'studios' => [ + [ + 'mal_id', + 'type', + 'name', + 'url' + ] + ], + 'genres' => [ + [ + 'mal_id', + 'type', + 'name', + 'url' + ] + ], + ]]); + } + + public function testCharacters() + { + $this->get('/v4/anime/1/characters') + ->seeStatusCode(200) + ->seeJsonStructure(['data'=>[ + [ + 'character' => [ + 'mal_id', + 'url', + 'images' => [ + 'jpg' => [ + 'image_url', + 'small_image_url', + ], + 'webp' => [ + 'image_url', + 'small_image_url', + ], + ], + 'name', + ], + 'role', + 'voice_actors' => [ + [ + 'person' => [ + 'mal_id', + 'images' => [ + 'jpg' => [ + 'image_url', + ], + ], + 'name' + ], + 'language' + ] + ] + ] + ]]); + } + + public function testStaff() + { + $this->get('/v4/anime/1/characters') + ->seeStatusCode(200) + ->seeJsonStructure(['data'=>[ + [ + 'person' => [ + 'mal_id', + 'images' => [ + 'jpg' => [ + 'image_url', + ], + ], + 'name' + ], + 'positions' + ] + ]]); + } + + public function testEpisodes() + { + $this->get('/v4/anime/1/episodes') + ->seeStatusCode(200) + ->seeJsonStructure([ + 'pagination' => [ + 'last_visible_page', + 'hast_next_page', + ], + 'data' => [ + [ + 'mal_id', + 'url', + 'title', + 'title_japanese', + 'title_romanji', + 'aired', + 'filler', + 'recap', + 'forum_url' + ] + ] + ]); + + $this->get('/v4/anime/21/episodes?page=2') + ->seeStatusCode(200) + ->seeJson([ + 'pagination' => [ + 'last_visible_page', + 'hast_next_page', + ], + 'data' => [ + [ + 'mal_id', + 'url', + 'title', + 'title_japanese', + 'title_romanji', + 'aired', + 'filler', + 'recap', + 'forum_url' + ] + ] + ]) + ->seeJsonContains([ + 'data' => [ + [ + 'mal_id' => 101, + 'title' => 'Showdown in a Heat Haze! Ace vs. the Gallant Scorpion!' + ] + ] + ]);; + } + + public function testEpisode() + { + $this->get('/v4/anime/21/episodes/1') + ->seeStatusCode(200) + ->seeJsonStructure([ + 'mal_id', + 'url', + 'title', + 'title_japanese', + 'title_romanji', + 'duration', + 'aired', + 'aired', + 'filler', + 'recap', + 'synopsis' + ]); + } + + public function testNews() + { + $this->get('/v4/anime/1/news') + ->seeStatusCode(200) + ->seeJsonStructure([ + 'pagination' => [ + 'last_visible_page', + 'hast_next_page', + ], + 'data' => [ + [ + 'mal_id', + 'url', + 'title', + 'date', + 'author_name', + 'author_url', + 'forum_url', + 'images' => [ + 'jpg' => [ + 'image_url', + ], + ], + 'comments', + 'excerpt' + ] + ] + ]); + } + + public function testPictures() + { + $this->get('/v4/anime/1/pictures') + ->seeStatusCode(200) + ->seeJsonStructure([ + 'images' => [ + [ + 'large_image_url', + 'small_image_url', + ] + ] + ]); + } + + public function testVideos() + { + $this->get('/v4/anime/1/videos') + ->seeStatusCode(200) + ->seeJsonStructure(['data'=>[ + 'promos' => [ + [ + 'title', + 'image_url', + 'trailer' => [ + 'youtube_id', + 'url', + 'embed_url', + 'images' => [ + 'default_image_url', + 'small_image_url', + 'medium_image_url', + 'large_image_url', + 'maximum_image_url', + ] + ], + ] + ], + 'episodes' => [ + [ + 'mal_id', + 'title', + 'episode', + 'url', + 'image_url', + ] + ] + ]]); + } + + public function testStats() + { + $this->get('/v4/anime/21/statistics') + ->seeStatusCode(200) + ->seeJsonStructure(['data'=>[ + 'watching', + 'completed', + 'on_hold', + 'dropped', + 'plan_to_watch', + 'total', + 'scores' => [ + [ + 'score', + 'votes', + 'percentage' + ] + ] + ]]); + } + + public function testForum() + { + $this->get('/v4/anime/1/forum') + ->seeStatusCode(200) + ->seeJsonStructure([ + 'data' => [ + [ + 'mal_id', + 'url', + 'title', + 'date', + 'author_name', + 'author_url', + 'comments', + 'last_comment' => [ + 'url', + 'author_name', + 'author_url', + 'date' + ] + ] + ] + ]); + } + + public function testMoreInfo() + { + $this->get('/v4/anime/1/moreinfo') + ->seeStatusCode(200) + ->seeJsonStructure(['data'=>[ + 'moreinfo' + ]]); + } + + public function testReviews() + { + $this->get('/v4/anime/1/reviews') + ->seeStatusCode(200) + ->seeJsonStructure([ + 'pagination' => [ + 'last_visible_page', + 'hast_next_page', + ], + 'data' => [ + [ + 'mal_id', + 'url', + 'votes', + 'date', + 'review', + 'episodes_watched', + 'scores' => [ + 'overall', + 'story', + 'animation', + 'sound', + 'character', + 'enjoyment' + ], + 'user' => [ + 'url', + 'username', + 'images' => [ + 'jpg' => [ + 'image_url', + ], + 'webp' => [ + 'image_url', + ], + ], + ] + ] + ] + ]); + + $this->get('/v4/anime/1/reviews?page=100') + ->seeStatusCode(200) + ->seeJsonStructure([ + 'pagination' => [ + 'last_visible_page', + 'hast_next_page', + ], + 'data' => [] + ]); + } + + public function testRecommendations() + { + $this->get('/v4/anime/1/recommendations') + ->seeStatusCode(200) + ->seeJsonStructure([ + 'data' => [ + [ + 'entry' => [ + 'mal_id', + 'url', + 'images' => [ + 'jpg' => [ + 'image_url', + 'small_image_url', + 'large_image_url' + ], + 'webp' => [ + 'image_url', + 'small_image_url', + 'large_image_url' + ], + ], + 'title' + ], + 'url', + 'votes', + ] + ] + ]); + } + + public function testAnimeUserUpdates() + { + $this->get('/v4/anime/1/userupdates') + ->seeStatusCode(200) + ->seeJsonStructure([ + 'pagination' => [ + 'last_visible_page', + 'hast_next_page', + ], + 'data' => [ + [ + 'user' => [ + 'username', + 'url', + 'images' => [ + 'jpg' => [ + 'image_url', + ], + 'webp' => [ + 'image_url', + ], + ], + ], + 'score', + 'status', + 'episodes_seen', + 'episodes_total', + 'date' + ] + ] + ]); + + $this->get('/v4/anime/1/userupdates?page=100') + ->seeStatusCode(404); + } + + public function testAnimeRelations() + { + $this->get('/v4/anime/1/relations') + ->seeStatusCode(200) + ->seeJsonStructure([ + 'data' => [ + [ + 'relation', + 'entry' => [ + [ + 'mal_id', + 'type', + 'name', + 'url' + ] + ], + ] + ] + ]); + } + + public function testAnimeThemes() + { + $this->get('/v4/anime/1/themes') + ->seeStatusCode(200) + ->seeJsonStructure([ + 'data' => [ + [ + 'openings', + 'endings', + ] + ] + ]); + } + + public function test404() + { + $this->get('/v4/anime/2') + ->seeStatusCode(404); + } +} diff --git a/tests/HttpV4/Controllers/CharacterControllerTest.php b/tests/HttpV4/Controllers/CharacterControllerTest.php new file mode 100644 index 0000000..c09d285 --- /dev/null +++ b/tests/HttpV4/Controllers/CharacterControllerTest.php @@ -0,0 +1,123 @@ +get('/v4/characters/1') + ->seeStatusCode(200) + ->seeJsonStructure(['data'=>[ + 'mal_id', + 'url', + 'images' => [ + 'jpg' => [ + 'image_url', + ], + 'webp' => [ + 'image_url', + ], + ], + 'name', + 'nicknames', + 'favorites', + 'about', + ]]); + } + + public function testAnimeography() + { + $this->get('/v4/characters/1/anime') + ->seeStatusCode(200) + ->seeJsonStructure(['data'=>[ + [ + 'role', + 'anime' => [ + 'mal_id', + 'url', + 'images' => [ + 'jpg' => [ + 'image_url', + 'small_image_url', + 'large_image_url' + ], + 'webp' => [ + 'image_url', + 'small_image_url', + 'large_image_url' + ], + ], + 'title' + ] + ] + ]]); + } + + public function testMangaography() + { + $this->get('/v4/characters/1/manga') + ->seeStatusCode(200) + ->seeJsonStructure(['data'=>[ + [ + 'role', + 'manga' => [ + 'mal_id', + 'url', + 'images' => [ + 'jpg' => [ + 'image_url', + 'small_image_url', + 'large_image_url' + ], + 'webp' => [ + 'image_url', + 'small_image_url', + 'large_image_url' + ], + ], + 'title' + ] + ] + ]]); + } + + public function testSeiyuu() + { + $this->get('/v4/characters/1/seiyuu') + ->seeStatusCode(200) + ->seeJsonStructure(['data'=>[ + [ + 'language', + 'person' => [ + 'mal_id', + 'url', + 'images' => [ + 'jpg' => [ + 'image_url', + ], + ], + 'name' + ] + ] + ]]); + } + + public function testPictures() + { + $this->get('/v4/characters/1/pictures') + ->seeStatusCode(200) + ->seeJsonStructure([ + 'data' => [ + [ + 'image_url', + 'large_image_url', + ] + ] + ]); + } + + public function test404() + { + $this->get('/v4/character/1000000') + ->seeStatusCode(404); + } +} diff --git a/tests/Http/Controllers/ClubControllerTest.php b/tests/HttpV4/Controllers/ClubControllerTest.php old mode 100755 new mode 100644 similarity index 65% rename from tests/Http/Controllers/ClubControllerTest.php rename to tests/HttpV4/Controllers/ClubControllerTest.php index c36eef4..2fde7fd --- a/tests/Http/Controllers/ClubControllerTest.php +++ b/tests/HttpV4/Controllers/ClubControllerTest.php @@ -4,12 +4,12 @@ class ClubControllerTest extends TestCase { public function testMain() { - $this->get('/v3/club/1') + $this->get('/v4/clubs/1') ->seeStatusCode(200) - ->seeJsonStructure([ + ->seeJsonStructure(['data'=>[ 'mal_id', 'url', - 'image_url', + 'images', 'title', 'members_count', 'pictures_count', @@ -24,7 +24,7 @@ class ClubControllerTest extends TestCase 'url', ] ], - 'anime_relations' => [ + 'anime' => [ [ 'mal_id', 'type', @@ -32,7 +32,7 @@ class ClubControllerTest extends TestCase 'url', ] ], - 'manga_relations' => [ + 'manga' => [ [ 'mal_id', 'type', @@ -40,7 +40,7 @@ class ClubControllerTest extends TestCase 'url', ] ], - 'character_relations' => [ + 'characters' => [ [ 'mal_id', 'type', @@ -48,30 +48,41 @@ class ClubControllerTest extends TestCase 'url', ] ], - ]); + ]]); } public function testMembers() { - $this->get('/v3/club/1/members') + $this->get('/v4/club/1/members') ->seeStatusCode(200) ->seeJsonStructure([ - 'members' => [ + 'pagination' => [ + 'last_visible_page', + 'hast_next_page', + ], + 'data' => [ [ 'username', 'url', - 'image_url' + 'images' => [ + 'jpg' => [ + 'image_url' + ], + 'webp' => [ + 'image_url' + ] + ], ] ] ]); - $this->get('/v3/club/1/members/1000') + $this->get('/v4/club/1/members/1000') ->seeStatusCode(404); } public function test404() { - $this->get('/v3/club/1000000') + $this->get('/v4/clubs/1000000') ->seeStatusCode(404); } } diff --git a/tests/HttpV4/Controllers/GenreControllerTest.php b/tests/HttpV4/Controllers/GenreControllerTest.php new file mode 100644 index 0000000..9cc4d88 --- /dev/null +++ b/tests/HttpV4/Controllers/GenreControllerTest.php @@ -0,0 +1,38 @@ +get('/v4/genres/anime') + ->seeStatusCode(200) + ->seeJsonStructure(['data'=>[ + [ + 'mal_id', + 'name', + 'url', + 'count' + ] + ]]); + } + + public function testMangaGenre() + { + $this->get('/v4/genres/manga') + ->seeStatusCode(200) + ->seeJsonStructure(['data'=>[ + [ + 'mal_id', + 'name', + 'url', + 'count' + ] + ]]); + } + + public function test404() + { + $this->get('/v4/genres') + ->seeStatusCode(404); + } +} diff --git a/tests/HttpV4/Controllers/MagazineControllerTest.php b/tests/HttpV4/Controllers/MagazineControllerTest.php new file mode 100644 index 0000000..c162254 --- /dev/null +++ b/tests/HttpV4/Controllers/MagazineControllerTest.php @@ -0,0 +1,18 @@ +get('/v4/magazines') + ->seeStatusCode(200) + ->seeJsonStructure(['data'=>[ + [ + 'mal_id', + 'name', + 'url', + 'count' + ] + ]]); + } +} diff --git a/tests/Http/Controllers/MangaControllerTest.php b/tests/HttpV4/Controllers/MangaControllerTest.php old mode 100755 new mode 100644 similarity index 50% rename from tests/Http/Controllers/MangaControllerTest.php rename to tests/HttpV4/Controllers/MangaControllerTest.php index 59da343..8c25721 --- a/tests/Http/Controllers/MangaControllerTest.php +++ b/tests/HttpV4/Controllers/MangaControllerTest.php @@ -1,22 +1,33 @@ get('/v3/manga/1') + $this->get('/v4/manga/1') ->seeStatusCode(200) ->seeJsonStructure([ 'mal_id', 'url', - 'image_url', + 'images' => [ + 'jpg' => [ + 'image_url', + 'small_image_url', + 'large_image_url' + ], + 'webp' => [ + 'image_url', + 'small_image_url', + 'large_image_url' + ], + ], 'title', 'title_english', 'title_japanese', 'title_synonyms', 'type', - 'volumes', 'chapters', + 'volumes', 'status', 'publishing', 'published' => [ @@ -44,25 +55,6 @@ class MangaControllerTest extends TestCase 'favorites', 'synopsis', 'background', - 'related' => [ - 'Adaptation' => [ - [ - 'mal_id', - 'type', - 'name', - 'url' - ] - ], - 'Side story' => [ - [ - 'mal_id', - 'type', - 'name', - 'url' - ] - ], - // ... - ], 'authors' => [ [ 'mal_id', @@ -92,37 +84,55 @@ class MangaControllerTest extends TestCase public function testCharacters() { - $this->get('/v3/manga/1/characters') + $this->get('/v4/manga/1/characters') ->seeStatusCode(200) - ->seeJsonStructure([ - 'characters' => [ - [ + ->seeJsonStructure(['data'=>[ + [ + 'character' => [ 'mal_id', 'url', - 'image_url', + 'images' => [ + 'jpg' => [ + 'image_url', + 'small_image_url', + ], + 'webp' => [ + 'image_url', + 'small_image_url', + ], + ], 'name', - 'role' - ] + ], + 'role', ] - ]); + ]]); } public function testNews() { - $this->get('/v3/manga/1/news') + $this->get('/v4/manga/1/news') ->seeStatusCode(200) ->seeJsonStructure([ - 'articles' => [ + 'pagination' => [ + 'last_visible_page', + 'hast_next_page', + ], + 'data' => [ [ + 'mal_id', 'url', 'title', 'date', 'author_name', 'author_url', 'forum_url', - 'image_url', + 'images' => [ + 'jpg' => [ + 'image_url', + ], + ], 'comments', - 'intro' + 'excerpt' ] ] ]); @@ -130,13 +140,13 @@ class MangaControllerTest extends TestCase public function testPictures() { - $this->get('/v3/manga/1/pictures') + $this->get('/v4/manga/1/pictures') ->seeStatusCode(200) ->seeJsonStructure([ - 'pictures' => [ + 'images' => [ [ - 'large', - 'small', + 'large_image_url', + 'small_image_url', ] ] ]); @@ -144,9 +154,9 @@ class MangaControllerTest extends TestCase public function testStats() { - $this->get('/v3/manga/1/stats') + $this->get('/v4/manga/1/statistics') ->seeStatusCode(200) - ->seeJsonStructure([ + ->seeJsonStructure(['data'=>[ 'reading', 'completed', 'on_hold', @@ -154,33 +164,34 @@ class MangaControllerTest extends TestCase 'plan_to_read', 'total', 'scores' => [ - 1 => [ + [ + 'score', 'votes', 'percentage' ] ] - ]); + ]]); } public function testForum() { - $this->get('/v3/manga/1/forum') + $this->get('/v4/manga/1/forum') ->seeStatusCode(200) ->seeJsonStructure([ - 'topics' => [ + 'data' => [ [ - 'topic_id', + 'mal_id', 'url', 'title', - 'date_posted', + 'date', 'author_name', 'author_url', - 'replies', - 'last_post' => [ + 'comments', + 'last_comment' => [ 'url', 'author_name', 'author_url', - 'date_posted' + 'date' ] ] ] @@ -189,62 +200,90 @@ class MangaControllerTest extends TestCase public function testMoreInfo() { - $this->get('/v3/manga/1/moreinfo') + $this->get('/v4/manga/1/moreinfo') ->seeStatusCode(200) - ->seeJsonStructure([ + ->seeJsonStructure(['data'=>[ 'moreinfo' - ]); + ]]); } public function testReviews() { - $this->get('/v3/manga/1/reviews') + $this->get('/v4/manga/1/reviews') ->seeStatusCode(200) ->seeJsonStructure([ - 'reviews' => [ + 'pagination' => [ + 'last_visible_page', + 'hast_next_page', + ], + 'data' => [ [ 'mal_id', 'url', - 'helpful_count', + 'votes', 'date', - 'reviewer' => [ - 'url', - 'image_url', - 'username', - 'chapters_read', - 'scores' => [ - 'overall', - 'story', - 'art', - 'character', - 'enjoyment' - ], + 'review', + 'chapters_read', + 'scores' => [ + 'overall', + 'story', + 'art', + 'character', + 'enjoyment' ], - 'content' + 'user' => [ + 'url', + 'username', + 'images' => [ + 'jpg' => [ + 'image_url', + ], + 'webp' => [ + 'image_url', + ], + ], + ] ] ] ]); - $this->get('/v3/manga/1/reviews/100') + $this->get('/v4/manga/1/reviews?page=100') ->seeStatusCode(200) ->seeJsonStructure([ - 'reviews' => [] + 'pagination' => [ + 'last_visible_page', + 'hast_next_page', + ], + 'data' => [] ]); } public function testRecommendations() { - $this->get('/v3/manga/1/recommendations') + $this->get('/v4/manga/1/recommendations') ->seeStatusCode(200) ->seeJsonStructure([ - 'recommendations' => [ + 'data' => [ [ - 'mal_id', + 'entry' => [ + 'mal_id', + 'url', + 'images' => [ + 'jpg' => [ + 'image_url', + 'small_image_url', + 'large_image_url' + ], + 'webp' => [ + 'image_url', + 'small_image_url', + 'large_image_url' + ], + ], + 'title' + ], 'url', - 'image_url', - 'recommendation_url', - 'title', - 'recommendation_count' + 'votes', ] ] ]); @@ -252,14 +291,27 @@ class MangaControllerTest extends TestCase public function testUserUpdates() { - $this->get('/v3/manga/1/userupdates') + $this->get('/v4/manga/1/userupdates') ->seeStatusCode(200) ->seeJsonStructure([ - 'users' => [ + 'pagination' => [ + 'last_visible_page', + 'hast_next_page', + ], + 'data' => [ [ - 'username', - 'url', - 'image_url', + 'user' => [ + 'username', + 'url', + 'images' => [ + 'jpg' => [ + 'image_url', + ], + 'webp' => [ + 'image_url', + ], + ], + ], 'score', 'status', 'volumes_read', @@ -271,13 +323,34 @@ class MangaControllerTest extends TestCase ] ]); - $this->get('/v3/manga/1/userupdates/1000') + $this->get('/v4/manga/1/userupdates?page=100') ->seeStatusCode(404); } + public function testMangaRelations() + { + $this->get('/v4/manga/1/relations') + ->seeStatusCode(200) + ->seeJsonStructure([ + 'data' => [ + [ + 'relation', + 'entry' => [ + [ + 'mal_id', + 'type', + 'name', + 'url' + ] + ], + ] + ] + ]); + } + public function test404() { - $this->get('/v3/manga/1000000') + $this->get('/v4/manga/1000000') ->seeStatusCode(404); } } diff --git a/tests/HttpV4/Controllers/PersonControllerTest.php b/tests/HttpV4/Controllers/PersonControllerTest.php new file mode 100644 index 0000000..a0d00b9 --- /dev/null +++ b/tests/HttpV4/Controllers/PersonControllerTest.php @@ -0,0 +1,147 @@ +get('/v4/people/1') + ->seeStatusCode(200) + ->seeJsonStructure(['data'=>[ + 'mal_id', + 'url', + 'website_url', + 'images' => [ + 'jpg' => [ + 'image_url', + ], + ], + 'name', + 'given_name', + 'family_name', + 'alternate_names', + 'birthday', + 'favorites', + 'about', + ]]); + } + + + public function testAnimeography() + { + $this->get('/v4/people/1/anime') + ->seeStatusCode(200) + ->seeJsonStructure(['data'=>[ + [ + 'position', + 'anime' => [ + 'mal_id', + 'url', + 'images' => [ + 'jpg' => [ + 'image_url', + 'small_image_url', + 'large_image_url' + ], + 'webp' => [ + 'image_url', + 'small_image_url', + 'large_image_url' + ], + ], + 'title' + ] + ] + ]]); + } + + public function testMangaography() + { + $this->get('/v4/characters/1/manga') + ->seeStatusCode(200) + ->seeJsonStructure(['data'=>[ + [ + 'position', + 'manga' => [ + 'mal_id', + 'url', + 'images' => [ + 'jpg' => [ + 'image_url', + 'small_image_url', + 'large_image_url' + ], + 'webp' => [ + 'image_url', + 'small_image_url', + 'large_image_url' + ], + ], + 'title' + ] + ] + ]]); + } + + public function testSeiyuu() + { + $this->get('/v4/people/1/seiyuu') + ->seeStatusCode(200) + ->seeJsonStructure(['data'=>[ + [ + 'role', + 'anime' => [ + 'mal_id', + 'url', + 'images' => [ + 'jpg' => [ + 'image_url', + 'small_image_url', + 'large_image_url' + ], + 'webp' => [ + 'image_url', + 'small_image_url', + 'large_image_url' + ], + ], + 'title' + ], + 'character' => [ + 'mal_id', + 'url', + 'images' => [ + 'jpg' => [ + 'image_url', + 'small_image_url', + ], + 'webp' => [ + 'image_url', + 'small_image_url', + ], + ], + 'name' + ] + ] + ]]); + } + + public function testPictures() + { + $this->get('/v4/people/1/pictures') + ->seeStatusCode(200) + ->seeJsonStructure([ + 'data' => [ + [ + 'large', + 'small', + ] + ] + ]); + } + + public function test404() + { + $this->get('/v4/people/1000000') + ->seeStatusCode(404); + } +} diff --git a/tests/HttpV4/Controllers/ProducerControllerTest.php b/tests/HttpV4/Controllers/ProducerControllerTest.php new file mode 100644 index 0000000..0e0d2e1 --- /dev/null +++ b/tests/HttpV4/Controllers/ProducerControllerTest.php @@ -0,0 +1,20 @@ +get('/v4/producers') + ->seeStatusCode(200) + ->seeJsonStructure(['data'=>[ + [ + 'mal_id', + 'name', + 'url', + 'count' + ] + ]]); + } + +} diff --git a/tests/HttpV4/Controllers/RecommendationsControllerTest.php b/tests/HttpV4/Controllers/RecommendationsControllerTest.php new file mode 100644 index 0000000..9e29437 --- /dev/null +++ b/tests/HttpV4/Controllers/RecommendationsControllerTest.php @@ -0,0 +1,48 @@ +get('/v4/recommendations/anime') + ->seeStatusCode(200) + ->seeJsonStructure([ + 'pagination' => [ + 'last_visible_page', + 'hast_next_page', + ], + 'data' => [ + [ + 'mal_id', + 'entry' => [ + [ + 'mal_id', + 'url', + 'images' => [ + 'jpg' => [ + 'image_url', + 'small_image_url', + 'large_image_url' + ], + 'webp' => [ + 'image_url', + 'small_image_url', + 'large_image_url' + ], + ], + 'title' + ] + ], + 'content', + 'date', + 'user' => [ + 'username', + 'url', + ], + ] + ] + ]); + } + +} \ No newline at end of file diff --git a/tests/HttpV4/Controllers/ReviewsControllerTest.php b/tests/HttpV4/Controllers/ReviewsControllerTest.php new file mode 100644 index 0000000..e368e6d --- /dev/null +++ b/tests/HttpV4/Controllers/ReviewsControllerTest.php @@ -0,0 +1,128 @@ +get('/v4/reviews/anime') + ->seeStatusCode(200) + ->seeJsonStructure([ + 'pagination' => [ + 'last_visible_page', + 'hast_next_page', + ], + 'data' => [ + [ + 'mal_id', + 'url', + 'type', + 'votes', + 'date', + 'review', + 'episodes_watched', + 'scores' => [ + 'overall', + 'story', + 'animation', + 'sound', + 'character', + 'enjoyment', + ], + 'entry' => [ + [ + 'mal_id', + 'url', + 'images' => [ + 'jpg' => [ + 'image_url', + 'small_image_url', + 'large_image_url' + ], + 'webp' => [ + 'image_url', + 'small_image_url', + 'large_image_url' + ], + ], + 'title' + ] + ], + 'user' => [ + 'username', + 'url', + 'images' => [ + 'jpg' => [ + 'image_url' + ], + 'webp' => [ + 'image_url' + ] + ] + ], + ] + ] + ]); + } + + public function testMangaReviews() + { + $this->get('/v4/reviews/manga') + ->seeStatusCode(200) + ->seeJsonStructure([ + 'pagination' => [ + 'last_visible_page', + 'hast_next_page', + ], + 'data' => [ + [ + 'mal_id', + 'url', + 'type', + 'votes', + 'date', + 'review', + 'chapters_read', + 'scores' => [ + 'overall', + 'story', + 'art', + 'character', + 'enjoyment', + ], + 'entry' => [ + [ + 'mal_id', + 'url', + 'images' => [ + 'jpg' => [ + 'image_url', + 'small_image_url', + 'large_image_url' + ], + 'webp' => [ + 'image_url', + 'small_image_url', + 'large_image_url' + ], + ], + 'title' + ] + ], + 'user' => [ + 'username', + 'url', + 'images' => [ + 'jpg' => [ + 'image_url' + ], + 'webp' => [ + 'image_url' + ] + ] + ], + ] + ] + ]); + } +} \ No newline at end of file diff --git a/tests/Http/Controllers/ScheduleControllerTest.php b/tests/HttpV4/Controllers/ScheduleControllerTest.php old mode 100755 new mode 100644 similarity index 95% rename from tests/Http/Controllers/ScheduleControllerTest.php rename to tests/HttpV4/Controllers/ScheduleControllerTest.php index 4d31ded..95334b5 --- a/tests/Http/Controllers/ScheduleControllerTest.php +++ b/tests/HttpV4/Controllers/ScheduleControllerTest.php @@ -4,7 +4,7 @@ class ScheduleControllerTest extends TestCase { public function testSchedule() { - $this->get('/v3/schedule') + $this->get('/v4/schedules') ->seeStatusCode(200) ->seeJsonStructure([ 'monday' => [ @@ -53,7 +53,7 @@ class ScheduleControllerTest extends TestCase public function test400() { - $this->get('/v3/schedule/asdjkhas') + $this->get('/v4/schedules/asdjkhas') ->seeStatusCode(400); } } diff --git a/tests/Http/Controllers/SearchControllerTest.php b/tests/HttpV4/Controllers/SearchControllerTest.php old mode 100755 new mode 100644 similarity index 91% rename from tests/Http/Controllers/SearchControllerTest.php rename to tests/HttpV4/Controllers/SearchControllerTest.php index 57a1c59..d569db9 --- a/tests/Http/Controllers/SearchControllerTest.php +++ b/tests/HttpV4/Controllers/SearchControllerTest.php @@ -4,7 +4,7 @@ class SearchControllerTest extends TestCase { public function testAnimeSearch() { - $this->get('/v3/search/anime?order_by=id&sort=asc') + $this->get('/v4/search/anime?order_by=id&sort=asc') ->seeStatusCode(200) ->seeJsonStructure([ 'results' => [ @@ -30,7 +30,7 @@ class SearchControllerTest extends TestCase public function testMangaSearch() { - $this->get('/v3/search/manga?order_by=id&sort=asc') + $this->get('/v4/search/manga?order_by=id&sort=asc') ->seeStatusCode(200) ->seeJsonStructure([ 'results' => [ @@ -56,7 +56,7 @@ class SearchControllerTest extends TestCase public function testPeopleSearch() { - $this->get('/v3/search/people?q=Sawano') + $this->get('/v4/search/people?q=Sawano') ->seeStatusCode(200) ->seeJsonStructure([ 'results' => [ @@ -74,7 +74,7 @@ class SearchControllerTest extends TestCase public function testCharacterSearch() { - $this->get('/v3/search/character?q=Okabe,%20Rintarou') + $this->get('/v4/search/character?q=Okabe,%20Rintarou') ->seeStatusCode(200) ->seeJsonStructure([ 'results' => [ diff --git a/tests/HttpV4/Controllers/SeasonControllerTest.php b/tests/HttpV4/Controllers/SeasonControllerTest.php new file mode 100644 index 0000000..56fe733 --- /dev/null +++ b/tests/HttpV4/Controllers/SeasonControllerTest.php @@ -0,0 +1,23 @@ +get('/v4/seasons') + ->seeStatusCode(200) + ->seeJsonStructure([ + 'data' => [ + 'year', + 'seasons' + ] + ]); + + // @todo add seasons test once database is populated + } + + public function testSchedule() + { + // @todo add schedules tests once database is populated + } +} diff --git a/tests/Http/Controllers/TopControllerTest.php b/tests/HttpV4/Controllers/TopControllerTest.php old mode 100755 new mode 100644 similarity index 93% rename from tests/Http/Controllers/TopControllerTest.php rename to tests/HttpV4/Controllers/TopControllerTest.php index 5a431b2..80a1c77 --- a/tests/Http/Controllers/TopControllerTest.php +++ b/tests/HttpV4/Controllers/TopControllerTest.php @@ -4,7 +4,7 @@ class TopControllerTest extends TestCase { public function testTopAnime() { - $this->get('/v3/top/anime') + $this->get('/v4/top/anime') ->seeStatusCode(200) ->seeJsonStructure([ 'top' => [ @@ -26,7 +26,7 @@ class TopControllerTest extends TestCase public function testTopManga() { - $this->get('/v3/top/manga') + $this->get('/v4/top/manga') ->seeStatusCode(200) ->seeJsonStructure([ 'top' => [ @@ -49,7 +49,7 @@ class TopControllerTest extends TestCase public function testTopPeople() { - $this->get('/v3/top/people') + $this->get('/v4/top/people') ->seeStatusCode(200) ->seeJsonStructure([ 'top' => [ @@ -69,7 +69,7 @@ class TopControllerTest extends TestCase public function testTopCharacters() { - $this->get('/v3/top/characters') + $this->get('/v4/top/characters') ->seeStatusCode(200) ->seeJsonStructure([ 'top' => [ @@ -105,7 +105,7 @@ class TopControllerTest extends TestCase public function test404() { - $this->get('/v3/top/anime/999') + $this->get('/v4/top/anime/999') ->seeStatusCode(404); } } diff --git a/tests/HttpV4/Controllers/UserControllerTest.php b/tests/HttpV4/Controllers/UserControllerTest.php new file mode 100644 index 0000000..2731a70 --- /dev/null +++ b/tests/HttpV4/Controllers/UserControllerTest.php @@ -0,0 +1,462 @@ +get('/v4/users/nekomata1037') + ->seeStatusCode(200) + ->seeJsonStructure(['data'=>[ + 'mal_id', + 'username', + 'url', + 'images' => [ + 'jpg' => [ + 'image_url' + ], + 'webp' => [ + 'image_url' + ] + ], + 'last_online', + 'gender', + 'birthday', + 'location', + 'joined', + ]]); + } + + public function testUserStatistics() + { + $this->get('/v4/users/nekomata1037/statistics') + ->seeStatusCode(200) + ->seeJsonStructure(['data'=>[ + + 'anime' => [ + 'days_watched', + 'mean_score', + 'watching', + 'completed', + 'on_hold', + 'dropped', + 'plan_to_watch', + 'total_entries', + 'rewatched', + 'episodes_watched' + ], + 'manga' => [ + 'days_read', + 'mean_score', + 'reading', + 'completed', + 'on_hold', + 'dropped', + 'plan_to_read', + 'total_entries', + 'reread', + 'chapters_read', + 'volumes_read' + ], + ]]); + } + + public function testUserAbout() + { + $this->get('/v4/users/nekomata1037/about') + ->seeStatusCode(200) + ->seeJsonStructure(['data'=>[ + 'about', + ]]); + } + + public function testUserFavorites() + { + $this->get('/v4/users/nekomata1037/favorites') + ->seeStatusCode(200) + ->seeJsonStructure(['data'=>[ + 'anime' => [ + [ + 'mal_id', + 'url', + 'images' => [ + 'jpg' => [ + 'image_url', + 'small_image_url', + 'large_image_url' + ], + 'webp' => [ + 'image_url', + 'small_image_url', + 'large_image_url' + ], + ], + 'title', + 'type', + 'start_year' + ] + ], + 'manga' => [ + [ + 'mal_id', + 'url', + 'images' => [ + 'jpg' => [ + 'image_url', + 'small_image_url', + 'large_image_url' + ], + 'webp' => [ + 'image_url', + 'small_image_url', + 'large_image_url' + ], + ], + 'title', + 'type', + 'start_year' + ] + ], + 'characters' => [ + [ + 'mal_id', + 'url', + 'images' => [ + 'jpg' => [ + 'image_url', + ], + 'webp' => [ + 'image_url', + ], + ], + 'name', + 'entry' => [ + 'mal_id', + 'title', + 'type', + 'url' + ] + ] + ], + 'people' => [ + [ + 'mal_id', + 'url', + 'images' => [ + 'jpg' => [ + 'image_url', + ], + ], + 'name', + ] + ] + + ]]); + } + + + + public function testUserHistory() + { + $this->get('/v4/users/purplepinapples/history/anime') + ->seeStatusCode(200) + ->seeJsonStructure([ + 'data' => [ + [ + 'entry' => [ + 'mal_id', + 'type', + 'name', + 'url' + ], + 'increment', + 'date' + ] + ] + ]); + + $this->get('/v4/users/nekomata1037/history/manga') + ->seeStatusCode(200) + ->seeJsonStructure([ + 'data' => [ + + ] + ]); + } + + public function testUserFriends() + { + $this->get('/v4/users/nekomata1037/friends') + ->seeStatusCode(200) + ->seeJsonStructure([ + 'pagination' => [ + 'last_visible_page', + 'hast_next_page', + ], + 'data' => [ + [ + 'user' => [ + 'username', + 'url', + 'images' => [ + 'jpg' => [ + 'image_url' + ], + 'webp' => [ + 'image_url' + ] + ] + ], + 'last_online', + 'friends_since' + ] + ] + ]); + + $this->get('/v4/users/nekomata1037/friends?page=200') + ->seeStatusCode(404); + } + + public function testUserAnimeList() + { + $this->get('/v4/users/nekomata1037/animelist?order_by=last_updated&sort=descending') + ->seeStatusCode(200) + ->seeJsonStructure([ + 'data' => [ + [ + 'watching_status', + 'score', + 'episodes_watched', + 'tags', + 'is_rewatching', + 'watch_start_date', + 'watch_end_date', + 'days', + 'storage', + 'priority', + 'anime' => [ + 'mal_id', + 'title', + 'url', + 'images' => [ + 'jpg' => [ + 'image_url', + 'small_image_url', + 'large_image_url' + ], + 'webp' => [ + 'image_url', + 'small_image_url', + 'large_image_url' + ], + ], + ], + 'type', + 'season', + 'year', + 'episodes', + 'rating', + 'airing', + 'aired' => [ + 'from', + 'to', + 'prop' => [ + 'from' => [ + 'day', + 'month', + 'year' + ], + 'to' => [ + 'day', + 'month', + 'year' + ] + ], + 'string' + ], + 'studios', + 'licensors' + ] + ] + ]); + } + + public function testUserMangaList() + { + $this->get('/v4/users/purplepinapples/mangalist?order_by=last_updated&sort=descending') + ->seeStatusCode(200) + ->seeJsonStructure([ + 'data' => [ + [ + 'reading_status', + 'score', + 'chapters_read', + 'tags', + 'is_rereading', + 'read_start_date', + 'read_end_date', + 'days', + 'retail', + 'priority', + 'manga' => [ + 'mal_id', + 'title', + 'url', + 'images' => [ + 'jpg' => [ + 'image_url', + 'small_image_url', + 'large_image_url' + ], + 'webp' => [ + 'image_url', + 'small_image_url', + 'large_image_url' + ], + ], + ], + 'type', + 'chapters', + 'volumes', + 'publishing', + 'published' => [ + 'from', + 'to', + 'prop' => [ + 'from' => [ + 'day', + 'month', + 'year' + ], + 'to' => [ + 'day', + 'month', + 'year' + ] + ], + 'string' + ], + 'magazines' + ] + ] + ]); + } + + public function testUserRecommendations() + { + $this->get('/v4/users/xinil/recommendations') + ->seeStatusCode(200) + ->seeJsonStructure([ + 'pagination' => [ + 'last_visible_page', + 'hast_next_page', + ], + 'data' => [ + [ + 'mal_id', + 'entry' => [ + [ + 'mal_id', + 'url', + 'images' => [ + 'jpg' => [ + 'image_url', + 'small_image_url', + 'large_image_url' + ], + 'webp' => [ + 'image_url', + 'small_image_url', + 'large_image_url' + ], + ], + 'title' + ] + ], + 'content', + 'date', + 'user' + ] + ] + ]); + + $this->get('/v4/users/xinil/recommendations?page=200') + ->seeStatusCode(200) + ->seeJsonStructure([ + 'pagination' => [ + 'last_visible_page', + 'hast_next_page', + ], + 'data' => [ + ] + ]); + } + + public function testUserReviews() + { + $this->get('/v4/users/xinil/reviews') + ->seeStatusCode(200) + ->seeJsonStructure([ + 'pagination' => [ + 'last_visible_page', + 'hast_next_page', + ], + 'data' => [ + [ + 'mal_id', + 'url', + 'type', + 'votes', + 'date', + 'review', + 'entry' => [ + [ + 'mal_id', + 'url', + 'images' => [ + 'jpg' => [ + 'image_url', + 'small_image_url', + 'large_image_url' + ], + 'webp' => [ + 'image_url', + 'small_image_url', + 'large_image_url' + ], + ], + 'title' + ] + ], + ] + ] + ]); + + $this->get('/v4/users/xinil/reviews?page=200') + ->seeStatusCode(404); + } + + public function testUserPrivateList() + { + $this->get('/v4/users/nekomata1037/mangalist?order_by=last_updated&sort=descending') + ->seeStatusCode(400); + } + + public function testUserClubs() + { + $this->get('/v4/users/nekomata1037/clubs') + ->seeStatusCode(200) + ->seeJsonStructure([ + 'pagination' => [ + 'last_visible_page', + 'hast_next_page', + ], + 'data' => [ + [ + 'mal_id', + 'name', + 'url', + ] + ] + ]); + } +} diff --git a/tests/HttpV4/Controllers/WatchControllerTest.php b/tests/HttpV4/Controllers/WatchControllerTest.php new file mode 100644 index 0000000..6c53345 --- /dev/null +++ b/tests/HttpV4/Controllers/WatchControllerTest.php @@ -0,0 +1,182 @@ +get('/v4/watch/episodes') + ->seeStatusCode(200) + ->seeJsonStructure([ + 'pagination' => [ + 'last_visible_page', + 'hast_next_page', + ], + 'data' => [ + [ + 'entry' => [ + [ + 'mal_id', + 'url', + 'images' => [ + 'jpg' => [ + 'image_url', + 'small_image_url', + 'large_image_url' + ], + 'webp' => [ + 'image_url', + 'small_image_url', + 'large_image_url' + ], + ], + 'title' + ] + ], + 'episodes' => [ + [ + 'mal_id', + 'url', + 'name', + 'premium' + ] + ], + ] + ] + ]); + + $this->get('/v4/watch/popular') + ->seeStatusCode(200) + ->seeJsonStructure([ + 'pagination' => [ + 'last_visible_page', + 'hast_next_page', + ], + 'data' => [ + [ + 'entry' => [ + [ + 'mal_id', + 'url', + 'images' => [ + 'jpg' => [ + 'image_url', + 'small_image_url', + 'large_image_url' + ], + 'webp' => [ + 'image_url', + 'small_image_url', + 'large_image_url' + ], + ], + 'title' + ] + ], + 'episodes' => [ + [ + 'mal_id', + 'url', + 'name', + 'premium' + ] + ], + ] + ] + ]); + } + + public function testWatchPromos() + { + $this->get('/v4/watch/promos') + ->seeStatusCode(200) + ->seeJsonStructure([ + 'pagination' => [ + 'last_visible_page', + 'hast_next_page', + ], + 'data' => [ + [ + 'title', + 'entry' => [ + [ + 'mal_id', + 'url', + 'images' => [ + 'jpg' => [ + 'image_url', + 'small_image_url', + 'large_image_url' + ], + 'webp' => [ + 'image_url', + 'small_image_url', + 'large_image_url' + ], + ], + 'title' + ] + ], + 'trailer' => [ + 'youtube_id', + 'url', + 'embed_url', + 'images' => [ + 'default_image_url', + 'small_image_url', + 'medium_image_url', + 'large_image_url', + 'maximum_image_url', + ] + ], + ] + ] + ]); + + $this->get('/v4/watch/popular') + ->seeStatusCode(200) + ->seeJsonStructure([ + 'pagination' => [ + 'last_visible_page', + 'hast_next_page', + ], + 'data' => [ + [ + 'title', + 'entry' => [ + [ + 'mal_id', + 'url', + 'images' => [ + 'jpg' => [ + 'image_url', + 'small_image_url', + 'large_image_url' + ], + 'webp' => [ + 'image_url', + 'small_image_url', + 'large_image_url' + ], + ], + 'title' + ] + ], + 'trailer' => [ + 'youtube_id', + 'url', + 'embed_url', + 'images' => [ + 'default_image_url', + 'small_image_url', + 'medium_image_url', + 'large_image_url', + 'maximum_image_url', + ] + ], + ] + ] + ]); + } + +} \ No newline at end of file