diff --git a/.github/workflows/run-tests-with-coverage.yml b/.github/workflows/run-tests-with-coverage.yml index d2e18b22..e59b2d28 100644 --- a/.github/workflows/run-tests-with-coverage.yml +++ b/.github/workflows/run-tests-with-coverage.yml @@ -9,9 +9,31 @@ on: jobs: tests-with-coverage: runs-on: ubuntu-latest + env: + DB_CONNECTION: pgsql + DB_HOST: 127.0.0.1 + DB_PORT: 5432 + DB_DATABASE: forge + DB_USERNAME: forge + DB_PASSWORD: secret + + services: + postgres: + image: postgres + env: + POSTGRES_DB: forge + POSTGRES_USER: forge + POSTGRES_PASSWORD: secret + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 strategy: matrix: - php-version: [ "8.2", "8.3", "8.4" ] + php-version: ["8.3", "8.4" ] steps: - name: Set up PHP uses: shivammathur/setup-php@v2 diff --git a/composer.json b/composer.json index 9b3bcd0e..c359e182 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,7 @@ } ], "require": { - "php": "^8.2", + "php": "^8.3", "alymosul/exponent-server-sdk-php": "1.3.*", "laravel/framework": ">=11.34" }, @@ -20,7 +20,7 @@ "mockery/mockery": "^1.6.12", "phpunit/phpunit": "^11.5.5", "orchestra/testbench": "^10.4", - "ronasit/laravel-helpers": "^2.7.1" + "ronasit/laravel-helpers": "^3.5.8" }, "autoload": { "psr-4": { diff --git a/composer.lock b/composer.lock index 6791bb13..50f79792 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "4dea22aa873f50730034db4aa3088cc6", + "content-hash": "90b106cb3dd0297a60f43900adb9f605", "packages": [ { "name": "alymosul/exponent-server-sdk-php", @@ -56,25 +56,25 @@ }, { "name": "brick/math", - "version": "0.13.1", + "version": "0.14.0", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "fc7ed316430118cc7836bf45faff18d5dfc8de04" + "reference": "113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/fc7ed316430118cc7836bf45faff18d5dfc8de04", - "reference": "fc7ed316430118cc7836bf45faff18d5dfc8de04", + "url": "https://api.github.com/repos/brick/math/zipball/113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2", + "reference": "113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2", "shasum": "" }, "require": { - "php": "^8.1" + "php": "^8.2" }, "require-dev": { "php-coveralls/php-coveralls": "^2.2", - "phpunit/phpunit": "^10.1", - "vimeo/psalm": "6.8.8" + "phpstan/phpstan": "2.1.22", + "phpunit/phpunit": "^11.5" }, "type": "library", "autoload": { @@ -104,7 +104,7 @@ ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.13.1" + "source": "https://github.com/brick/math/tree/0.14.0" }, "funding": [ { @@ -112,30 +112,30 @@ "type": "github" } ], - "time": "2025-03-29T13:50:30+00:00" + "time": "2025-08-29T12:40:03+00:00" }, { "name": "carbonphp/carbon-doctrine-types", - "version": "2.1.0", + "version": "3.2.0", "source": { "type": "git", "url": "https://github.com/CarbonPHP/carbon-doctrine-types.git", - "reference": "99f76ffa36cce3b70a4a6abce41dba15ca2e84cb" + "reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/CarbonPHP/carbon-doctrine-types/zipball/99f76ffa36cce3b70a4a6abce41dba15ca2e84cb", - "reference": "99f76ffa36cce3b70a4a6abce41dba15ca2e84cb", + "url": "https://api.github.com/repos/CarbonPHP/carbon-doctrine-types/zipball/18ba5ddfec8976260ead6e866180bd5d2f71aa1d", + "reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d", "shasum": "" }, "require": { - "php": "^7.4 || ^8.0" + "php": "^8.1" }, "conflict": { - "doctrine/dbal": "<3.7.0 || >=4.0.0" + "doctrine/dbal": "<4.0.0 || >=5.0.0" }, "require-dev": { - "doctrine/dbal": "^3.7.0", + "doctrine/dbal": "^4.0.0", "nesbot/carbon": "^2.71.0 || ^3.0.0", "phpunit/phpunit": "^10.3" }, @@ -165,7 +165,7 @@ ], "support": { "issues": "https://github.com/CarbonPHP/carbon-doctrine-types/issues", - "source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/2.1.0" + "source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/3.2.0" }, "funding": [ { @@ -181,7 +181,7 @@ "type": "tidelift" } ], - "time": "2023-12-11T17:09:12+00:00" + "time": "2024-02-09T16:56:22+00:00" }, { "name": "dflydev/dot-access-data", @@ -1103,20 +1103,20 @@ }, { "name": "laravel/framework", - "version": "v12.25.0", + "version": "v12.33.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "2ee2ba94ae60efd24c7a787cbb1a2f82f714bb20" + "reference": "124efc5f09d4668a4dc13f94a1018c524a58bcb1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/2ee2ba94ae60efd24c7a787cbb1a2f82f714bb20", - "reference": "2ee2ba94ae60efd24c7a787cbb1a2f82f714bb20", + "url": "https://api.github.com/repos/laravel/framework/zipball/124efc5f09d4668a4dc13f94a1018c524a58bcb1", + "reference": "124efc5f09d4668a4dc13f94a1018c524a58bcb1", "shasum": "" }, "require": { - "brick/math": "^0.11|^0.12|^0.13", + "brick/math": "^0.11|^0.12|^0.13|^0.14", "composer-runtime-api": "^2.2", "doctrine/inflector": "^2.0.5", "dragonmantank/cron-expression": "^3.4", @@ -1152,9 +1152,9 @@ "symfony/http-kernel": "^7.2.0", "symfony/mailer": "^7.2.0", "symfony/mime": "^7.2.0", - "symfony/polyfill-php83": "^1.31", - "symfony/polyfill-php84": "^1.31", - "symfony/polyfill-php85": "^1.31", + "symfony/polyfill-php83": "^1.33", + "symfony/polyfill-php84": "^1.33", + "symfony/polyfill-php85": "^1.33", "symfony/process": "^7.2.0", "symfony/routing": "^7.2.0", "symfony/uid": "^7.2.0", @@ -1190,6 +1190,7 @@ "illuminate/filesystem": "self.version", "illuminate/hashing": "self.version", "illuminate/http": "self.version", + "illuminate/json-schema": "self.version", "illuminate/log": "self.version", "illuminate/macroable": "self.version", "illuminate/mail": "self.version", @@ -1222,7 +1223,8 @@ "league/flysystem-read-only": "^3.25.1", "league/flysystem-sftp-v3": "^3.25.1", "mockery/mockery": "^1.6.10", - "orchestra/testbench-core": "^10.6.0", + "opis/json-schema": "^2.4.1", + "orchestra/testbench-core": "^10.6.5", "pda/pheanstalk": "^5.0.6|^7.0.0", "php-http/discovery": "^1.15", "phpstan/phpstan": "^2.0", @@ -1247,7 +1249,7 @@ "ext-pdo": "Required to use all database features.", "ext-posix": "Required to use all features of the queue worker.", "ext-redis": "Required to use the Redis cache and queue drivers (^4.0|^5.0|^6.0).", - "fakerphp/faker": "Required to use the eloquent factory builder (^1.9.1).", + "fakerphp/faker": "Required to generate fake data using the fake() helper (^1.23).", "filp/whoops": "Required for friendly error pages in development (^2.14.3).", "laravel/tinker": "Required to use the tinker console command (^2.0).", "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^3.25.1).", @@ -1316,20 +1318,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-08-18T22:20:52+00:00" + "time": "2025-10-07T14:30:39+00:00" }, { "name": "laravel/prompts", - "version": "v0.3.6", + "version": "v0.3.7", "source": { "type": "git", "url": "https://github.com/laravel/prompts.git", - "reference": "86a8b692e8661d0fb308cec64f3d176821323077" + "reference": "a1891d362714bc40c8d23b0b1d7090f022ea27cc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/prompts/zipball/86a8b692e8661d0fb308cec64f3d176821323077", - "reference": "86a8b692e8661d0fb308cec64f3d176821323077", + "url": "https://api.github.com/repos/laravel/prompts/zipball/a1891d362714bc40c8d23b0b1d7090f022ea27cc", + "reference": "a1891d362714bc40c8d23b0b1d7090f022ea27cc", "shasum": "" }, "require": { @@ -1346,8 +1348,8 @@ "illuminate/collections": "^10.0|^11.0|^12.0", "mockery/mockery": "^1.5", "pestphp/pest": "^2.3|^3.4", - "phpstan/phpstan": "^1.11", - "phpstan/phpstan-mockery": "^1.1" + "phpstan/phpstan": "^1.12.28", + "phpstan/phpstan-mockery": "^1.1.3" }, "suggest": { "ext-pcntl": "Required for the spinner to be animated." @@ -1373,22 +1375,22 @@ "description": "Add beautiful and user-friendly forms to your command-line applications.", "support": { "issues": "https://github.com/laravel/prompts/issues", - "source": "https://github.com/laravel/prompts/tree/v0.3.6" + "source": "https://github.com/laravel/prompts/tree/v0.3.7" }, - "time": "2025-07-07T14:17:42+00:00" + "time": "2025-09-19T13:47:56+00:00" }, { "name": "laravel/serializable-closure", - "version": "v2.0.4", + "version": "v2.0.5", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "b352cf0534aa1ae6b4d825d1e762e35d43f8a841" + "reference": "3832547db6e0e2f8bb03d4093857b378c66eceed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/b352cf0534aa1ae6b4d825d1e762e35d43f8a841", - "reference": "b352cf0534aa1ae6b4d825d1e762e35d43f8a841", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/3832547db6e0e2f8bb03d4093857b378c66eceed", + "reference": "3832547db6e0e2f8bb03d4093857b378c66eceed", "shasum": "" }, "require": { @@ -1436,7 +1438,7 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2025-03-19T13:51:03+00:00" + "time": "2025-09-22T17:29:40+00:00" }, { "name": "league/commonmark", @@ -2094,16 +2096,16 @@ }, { "name": "nesbot/carbon", - "version": "3.10.2", + "version": "3.10.3", "source": { "type": "git", "url": "https://github.com/CarbonPHP/carbon.git", - "reference": "76b5c07b8a9d2025ed1610e14cef1f3fd6ad2c24" + "reference": "8e3643dcd149ae0fe1d2ff4f2c8e4bbfad7c165f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/76b5c07b8a9d2025ed1610e14cef1f3fd6ad2c24", - "reference": "76b5c07b8a9d2025ed1610e14cef1f3fd6ad2c24", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/8e3643dcd149ae0fe1d2ff4f2c8e4bbfad7c165f", + "reference": "8e3643dcd149ae0fe1d2ff4f2c8e4bbfad7c165f", "shasum": "" }, "require": { @@ -2121,13 +2123,13 @@ "require-dev": { "doctrine/dbal": "^3.6.3 || ^4.0", "doctrine/orm": "^2.15.2 || ^3.0", - "friendsofphp/php-cs-fixer": "^3.75.0", + "friendsofphp/php-cs-fixer": "^v3.87.1", "kylekatarnls/multi-tester": "^2.5.3", "phpmd/phpmd": "^2.15.0", "phpstan/extension-installer": "^1.4.3", - "phpstan/phpstan": "^2.1.17", - "phpunit/phpunit": "^10.5.46", - "squizlabs/php_codesniffer": "^3.13.0" + "phpstan/phpstan": "^2.1.22", + "phpunit/phpunit": "^10.5.53", + "squizlabs/php_codesniffer": "^3.13.4" }, "bin": [ "bin/carbon" @@ -2195,7 +2197,7 @@ "type": "tidelift" } ], - "time": "2025-08-02T09:36:06+00:00" + "time": "2025-09-06T13:39:36+00:00" }, { "name": "nette/schema", @@ -3044,20 +3046,20 @@ }, { "name": "ramsey/uuid", - "version": "4.9.0", + "version": "4.9.1", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "4e0e23cc785f0724a0e838279a9eb03f28b092a0" + "reference": "81f941f6f729b1e3ceea61d9d014f8b6c6800440" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/4e0e23cc785f0724a0e838279a9eb03f28b092a0", - "reference": "4e0e23cc785f0724a0e838279a9eb03f28b092a0", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/81f941f6f729b1e3ceea61d9d014f8b6c6800440", + "reference": "81f941f6f729b1e3ceea61d9d014f8b6c6800440", "shasum": "" }, "require": { - "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13", + "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14", "php": "^8.0", "ramsey/collection": "^1.2 || ^2.0" }, @@ -3116,9 +3118,9 @@ ], "support": { "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.9.0" + "source": "https://github.com/ramsey/uuid/tree/4.9.1" }, - "time": "2025-06-25T14:20:11+00:00" + "time": "2025-09-04T20:59:21+00:00" }, { "name": "symfony/clock", @@ -3196,16 +3198,16 @@ }, { "name": "symfony/console", - "version": "v7.3.2", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "5f360ebc65c55265a74d23d7fe27f957870158a1" + "reference": "2b9c5fafbac0399a20a2e82429e2bd735dcfb7db" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/5f360ebc65c55265a74d23d7fe27f957870158a1", - "reference": "5f360ebc65c55265a74d23d7fe27f957870158a1", + "url": "https://api.github.com/repos/symfony/console/zipball/2b9c5fafbac0399a20a2e82429e2bd735dcfb7db", + "reference": "2b9c5fafbac0399a20a2e82429e2bd735dcfb7db", "shasum": "" }, "require": { @@ -3270,7 +3272,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.3.2" + "source": "https://github.com/symfony/console/tree/v7.3.4" }, "funding": [ { @@ -3290,7 +3292,7 @@ "type": "tidelift" } ], - "time": "2025-07-30T17:13:41+00:00" + "time": "2025-09-22T15:31:00+00:00" }, { "name": "symfony/css-selector", @@ -3426,16 +3428,16 @@ }, { "name": "symfony/error-handler", - "version": "v7.3.2", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "0b31a944fcd8759ae294da4d2808cbc53aebd0c3" + "reference": "99f81bc944ab8e5dae4f21b4ca9972698bbad0e4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/0b31a944fcd8759ae294da4d2808cbc53aebd0c3", - "reference": "0b31a944fcd8759ae294da4d2808cbc53aebd0c3", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/99f81bc944ab8e5dae4f21b4ca9972698bbad0e4", + "reference": "99f81bc944ab8e5dae4f21b4ca9972698bbad0e4", "shasum": "" }, "require": { @@ -3483,7 +3485,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/v7.3.2" + "source": "https://github.com/symfony/error-handler/tree/v7.3.4" }, "funding": [ { @@ -3503,20 +3505,20 @@ "type": "tidelift" } ], - "time": "2025-07-07T08:17:57+00:00" + "time": "2025-09-11T10:12:26+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v7.3.0", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "497f73ac996a598c92409b44ac43b6690c4f666d" + "reference": "b7dc69e71de420ac04bc9ab830cf3ffebba48191" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/497f73ac996a598c92409b44ac43b6690c4f666d", - "reference": "497f73ac996a598c92409b44ac43b6690c4f666d", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/b7dc69e71de420ac04bc9ab830cf3ffebba48191", + "reference": "b7dc69e71de420ac04bc9ab830cf3ffebba48191", "shasum": "" }, "require": { @@ -3567,7 +3569,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/v7.3.0" + "source": "https://github.com/symfony/event-dispatcher/tree/v7.3.3" }, "funding": [ { @@ -3578,12 +3580,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-22T09:11:45+00:00" + "time": "2025-08-13T11:49:31+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -3731,16 +3737,16 @@ }, { "name": "symfony/http-foundation", - "version": "v7.3.2", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "6877c122b3a6cc3695849622720054f6e6fa5fa6" + "reference": "c061c7c18918b1b64268771aad04b40be41dd2e6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/6877c122b3a6cc3695849622720054f6e6fa5fa6", - "reference": "6877c122b3a6cc3695849622720054f6e6fa5fa6", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/c061c7c18918b1b64268771aad04b40be41dd2e6", + "reference": "c061c7c18918b1b64268771aad04b40be41dd2e6", "shasum": "" }, "require": { @@ -3790,7 +3796,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.3.2" + "source": "https://github.com/symfony/http-foundation/tree/v7.3.4" }, "funding": [ { @@ -3810,20 +3816,20 @@ "type": "tidelift" } ], - "time": "2025-07-10T08:47:49+00:00" + "time": "2025-09-16T08:38:17+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.3.2", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "6ecc895559ec0097e221ed2fd5eb44d5fede083c" + "reference": "b796dffea7821f035047235e076b60ca2446e3cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/6ecc895559ec0097e221ed2fd5eb44d5fede083c", - "reference": "6ecc895559ec0097e221ed2fd5eb44d5fede083c", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/b796dffea7821f035047235e076b60ca2446e3cf", + "reference": "b796dffea7821f035047235e076b60ca2446e3cf", "shasum": "" }, "require": { @@ -3908,7 +3914,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/v7.3.2" + "source": "https://github.com/symfony/http-kernel/tree/v7.3.4" }, "funding": [ { @@ -3928,20 +3934,20 @@ "type": "tidelift" } ], - "time": "2025-07-31T10:45:04+00:00" + "time": "2025-09-27T12:32:17+00:00" }, { "name": "symfony/mailer", - "version": "v7.3.2", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "d43e84d9522345f96ad6283d5dfccc8c1cfc299b" + "reference": "ab97ef2f7acf0216955f5845484235113047a31d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/d43e84d9522345f96ad6283d5dfccc8c1cfc299b", - "reference": "d43e84d9522345f96ad6283d5dfccc8c1cfc299b", + "url": "https://api.github.com/repos/symfony/mailer/zipball/ab97ef2f7acf0216955f5845484235113047a31d", + "reference": "ab97ef2f7acf0216955f5845484235113047a31d", "shasum": "" }, "require": { @@ -3992,7 +3998,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v7.3.2" + "source": "https://github.com/symfony/mailer/tree/v7.3.4" }, "funding": [ { @@ -4012,20 +4018,20 @@ "type": "tidelift" } ], - "time": "2025-07-15T11:36:08+00:00" + "time": "2025-09-17T05:51:54+00:00" }, { "name": "symfony/mime", - "version": "v7.3.2", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "e0a0f859148daf1edf6c60b398eb40bfc96697d1" + "reference": "b1b828f69cbaf887fa835a091869e55df91d0e35" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/e0a0f859148daf1edf6c60b398eb40bfc96697d1", - "reference": "e0a0f859148daf1edf6c60b398eb40bfc96697d1", + "url": "https://api.github.com/repos/symfony/mime/zipball/b1b828f69cbaf887fa835a091869e55df91d0e35", + "reference": "b1b828f69cbaf887fa835a091869e55df91d0e35", "shasum": "" }, "require": { @@ -4080,7 +4086,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v7.3.2" + "source": "https://github.com/symfony/mime/tree/v7.3.4" }, "funding": [ { @@ -4100,7 +4106,7 @@ "type": "tidelift" } ], - "time": "2025-07-15T13:41:35+00:00" + "time": "2025-09-16T08:38:17+00:00" }, { "name": "symfony/polyfill-ctype", @@ -4933,16 +4939,16 @@ }, { "name": "symfony/process", - "version": "v7.3.0", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "40c295f2deb408d5e9d2d32b8ba1dd61e36f05af" + "reference": "f24f8f316367b30810810d4eb30c543d7003ff3b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/40c295f2deb408d5e9d2d32b8ba1dd61e36f05af", - "reference": "40c295f2deb408d5e9d2d32b8ba1dd61e36f05af", + "url": "https://api.github.com/repos/symfony/process/zipball/f24f8f316367b30810810d4eb30c543d7003ff3b", + "reference": "f24f8f316367b30810810d4eb30c543d7003ff3b", "shasum": "" }, "require": { @@ -4974,7 +4980,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.3.0" + "source": "https://github.com/symfony/process/tree/v7.3.4" }, "funding": [ { @@ -4985,25 +4991,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-17T09:11:12+00:00" + "time": "2025-09-11T10:12:26+00:00" }, { "name": "symfony/routing", - "version": "v7.3.2", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "7614b8ca5fa89b9cd233e21b627bfc5774f586e4" + "reference": "8dc648e159e9bac02b703b9fbd937f19ba13d07c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/7614b8ca5fa89b9cd233e21b627bfc5774f586e4", - "reference": "7614b8ca5fa89b9cd233e21b627bfc5774f586e4", + "url": "https://api.github.com/repos/symfony/routing/zipball/8dc648e159e9bac02b703b9fbd937f19ba13d07c", + "reference": "8dc648e159e9bac02b703b9fbd937f19ba13d07c", "shasum": "" }, "require": { @@ -5055,7 +5065,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v7.3.2" + "source": "https://github.com/symfony/routing/tree/v7.3.4" }, "funding": [ { @@ -5075,7 +5085,7 @@ "type": "tidelift" } ], - "time": "2025-07-15T11:36:08+00:00" + "time": "2025-09-11T10:12:26+00:00" }, { "name": "symfony/service-contracts", @@ -5162,16 +5172,16 @@ }, { "name": "symfony/string", - "version": "v7.3.2", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "42f505aff654e62ac7ac2ce21033818297ca89ca" + "reference": "f96476035142921000338bad71e5247fbc138872" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/42f505aff654e62ac7ac2ce21033818297ca89ca", - "reference": "42f505aff654e62ac7ac2ce21033818297ca89ca", + "url": "https://api.github.com/repos/symfony/string/zipball/f96476035142921000338bad71e5247fbc138872", + "reference": "f96476035142921000338bad71e5247fbc138872", "shasum": "" }, "require": { @@ -5186,7 +5196,6 @@ }, "require-dev": { "symfony/emoji": "^7.1", - "symfony/error-handler": "^6.4|^7.0", "symfony/http-client": "^6.4|^7.0", "symfony/intl": "^6.4|^7.0", "symfony/translation-contracts": "^2.5|^3.0", @@ -5229,7 +5238,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.3.2" + "source": "https://github.com/symfony/string/tree/v7.3.4" }, "funding": [ { @@ -5249,20 +5258,20 @@ "type": "tidelift" } ], - "time": "2025-07-10T08:47:49+00:00" + "time": "2025-09-11T14:36:48+00:00" }, { "name": "symfony/translation", - "version": "v7.3.2", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "81b48f4daa96272efcce9c7a6c4b58e629df3c90" + "reference": "ec25870502d0c7072d086e8ffba1420c85965174" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/81b48f4daa96272efcce9c7a6c4b58e629df3c90", - "reference": "81b48f4daa96272efcce9c7a6c4b58e629df3c90", + "url": "https://api.github.com/repos/symfony/translation/zipball/ec25870502d0c7072d086e8ffba1420c85965174", + "reference": "ec25870502d0c7072d086e8ffba1420c85965174", "shasum": "" }, "require": { @@ -5329,7 +5338,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v7.3.2" + "source": "https://github.com/symfony/translation/tree/v7.3.4" }, "funding": [ { @@ -5349,7 +5358,7 @@ "type": "tidelift" } ], - "time": "2025-07-30T17:31:46+00:00" + "time": "2025-09-07T11:39:36+00:00" }, { "name": "symfony/translation-contracts", @@ -5505,16 +5514,16 @@ }, { "name": "symfony/var-dumper", - "version": "v7.3.2", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "53205bea27450dc5c65377518b3275e126d45e75" + "reference": "b8abe7daf2730d07dfd4b2ee1cecbf0dd2fbdabb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/53205bea27450dc5c65377518b3275e126d45e75", - "reference": "53205bea27450dc5c65377518b3275e126d45e75", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/b8abe7daf2730d07dfd4b2ee1cecbf0dd2fbdabb", + "reference": "b8abe7daf2730d07dfd4b2ee1cecbf0dd2fbdabb", "shasum": "" }, "require": { @@ -5568,7 +5577,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.3.2" + "source": "https://github.com/symfony/var-dumper/tree/v7.3.4" }, "funding": [ { @@ -5588,7 +5597,7 @@ "type": "tidelift" } ], - "time": "2025-07-29T20:02:46+00:00" + "time": "2025-09-11T10:12:26+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -6021,48 +6030,40 @@ }, { "name": "doctrine/dbal", - "version": "3.10.1", + "version": "4.3.4", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "3626601014388095d3af9de7e9e958623b7ef005" + "reference": "1a2fbd0e93b8dec7c3d1ac2b6396a7b929b130dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/3626601014388095d3af9de7e9e958623b7ef005", - "reference": "3626601014388095d3af9de7e9e958623b7ef005", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/1a2fbd0e93b8dec7c3d1ac2b6396a7b929b130dc", + "reference": "1a2fbd0e93b8dec7c3d1ac2b6396a7b929b130dc", "shasum": "" }, "require": { - "composer-runtime-api": "^2", - "doctrine/deprecations": "^0.5.3|^1", - "doctrine/event-manager": "^1|^2", - "php": "^7.4 || ^8.0", + "doctrine/deprecations": "^1.1.5", + "php": "^8.2", "psr/cache": "^1|^2|^3", "psr/log": "^1|^2|^3" }, - "conflict": { - "doctrine/cache": "< 1.11" - }, "require-dev": { - "doctrine/cache": "^1.11|^2.0", - "doctrine/coding-standard": "13.0.0", + "doctrine/coding-standard": "14.0.0", "fig/log-test": "^1", - "jetbrains/phpstorm-stubs": "2023.1", - "phpstan/phpstan": "2.1.17", + "jetbrains/phpstorm-stubs": "2023.2", + "phpstan/phpstan": "2.1.30", + "phpstan/phpstan-phpunit": "2.0.7", "phpstan/phpstan-strict-rules": "^2", - "phpunit/phpunit": "9.6.23", - "slevomat/coding-standard": "8.16.2", - "squizlabs/php_codesniffer": "3.13.1", - "symfony/cache": "^5.4|^6.0|^7.0", - "symfony/console": "^4.4|^5.4|^6.0|^7.0" + "phpunit/phpunit": "11.5.23", + "slevomat/coding-standard": "8.24.0", + "squizlabs/php_codesniffer": "4.0.0", + "symfony/cache": "^6.3.8|^7.0", + "symfony/console": "^5.4|^6.3|^7.0" }, "suggest": { "symfony/console": "For helpful console commands such as SQL execution and import of files." }, - "bin": [ - "bin/doctrine-dbal" - ], "type": "library", "autoload": { "psr-4": { @@ -6115,7 +6116,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.10.1" + "source": "https://github.com/doctrine/dbal/tree/4.3.4" }, "funding": [ { @@ -6131,7 +6132,7 @@ "type": "tidelift" } ], - "time": "2025-08-05T12:18:06+00:00" + "time": "2025-10-09T09:11:36+00:00" }, { "name": "doctrine/deprecations", @@ -6181,97 +6182,6 @@ }, "time": "2025-04-07T20:06:18+00:00" }, - { - "name": "doctrine/event-manager", - "version": "2.0.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/event-manager.git", - "reference": "b680156fa328f1dfd874fd48c7026c41570b9c6e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/event-manager/zipball/b680156fa328f1dfd874fd48c7026c41570b9c6e", - "reference": "b680156fa328f1dfd874fd48c7026c41570b9c6e", - "shasum": "" - }, - "require": { - "php": "^8.1" - }, - "conflict": { - "doctrine/common": "<2.9" - }, - "require-dev": { - "doctrine/coding-standard": "^12", - "phpstan/phpstan": "^1.8.8", - "phpunit/phpunit": "^10.5", - "vimeo/psalm": "^5.24" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - }, - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com" - } - ], - "description": "The Doctrine Event Manager is a simple PHP event system that was built to be used with the various Doctrine projects.", - "homepage": "https://www.doctrine-project.org/projects/event-manager.html", - "keywords": [ - "event", - "event dispatcher", - "event manager", - "event system", - "events" - ], - "support": { - "issues": "https://github.com/doctrine/event-manager/issues", - "source": "https://github.com/doctrine/event-manager/tree/2.0.1" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fevent-manager", - "type": "tidelift" - } - ], - "time": "2024-05-22T20:47:39+00:00" - }, { "name": "ezyang/htmlpurifier", "version": "v4.18.0", @@ -6665,16 +6575,16 @@ }, { "name": "maatwebsite/excel", - "version": "3.1.66", + "version": "3.1.67", "source": { "type": "git", "url": "https://github.com/SpartnerNL/Laravel-Excel.git", - "reference": "3b29c2426a46674f444890c45f742452a396aae8" + "reference": "e508e34a502a3acc3329b464dad257378a7edb4d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/SpartnerNL/Laravel-Excel/zipball/3b29c2426a46674f444890c45f742452a396aae8", - "reference": "3b29c2426a46674f444890c45f742452a396aae8", + "url": "https://api.github.com/repos/SpartnerNL/Laravel-Excel/zipball/e508e34a502a3acc3329b464dad257378a7edb4d", + "reference": "e508e34a502a3acc3329b464dad257378a7edb4d", "shasum": "" }, "require": { @@ -6682,7 +6592,7 @@ "ext-json": "*", "illuminate/support": "5.8.*||^6.0||^7.0||^8.0||^9.0||^10.0||^11.0||^12.0", "php": "^7.0||^8.0", - "phpoffice/phpspreadsheet": "^1.29.12", + "phpoffice/phpspreadsheet": "^1.30.0", "psr/simple-cache": "^1.0||^2.0||^3.0" }, "require-dev": { @@ -6730,7 +6640,7 @@ ], "support": { "issues": "https://github.com/SpartnerNL/Laravel-Excel/issues", - "source": "https://github.com/SpartnerNL/Laravel-Excel/tree/3.1.66" + "source": "https://github.com/SpartnerNL/Laravel-Excel/tree/3.1.67" }, "funding": [ { @@ -6742,26 +6652,26 @@ "type": "github" } ], - "time": "2025-08-07T08:31:22+00:00" + "time": "2025-08-26T09:13:16+00:00" }, { "name": "maennchen/zipstream-php", - "version": "3.1.2", + "version": "3.2.0", "source": { "type": "git", "url": "https://github.com/maennchen/ZipStream-PHP.git", - "reference": "aeadcf5c412332eb426c0f9b4485f6accba2a99f" + "reference": "9712d8fa4cdf9240380b01eb4be55ad8dcf71416" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/aeadcf5c412332eb426c0f9b4485f6accba2a99f", - "reference": "aeadcf5c412332eb426c0f9b4485f6accba2a99f", + "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/9712d8fa4cdf9240380b01eb4be55ad8dcf71416", + "reference": "9712d8fa4cdf9240380b01eb4be55ad8dcf71416", "shasum": "" }, "require": { "ext-mbstring": "*", "ext-zlib": "*", - "php-64bit": "^8.2" + "php-64bit": "^8.3" }, "require-dev": { "brianium/paratest": "^7.7", @@ -6770,7 +6680,7 @@ "guzzlehttp/guzzle": "^7.5", "mikey179/vfsstream": "^1.6", "php-coveralls/php-coveralls": "^2.5", - "phpunit/phpunit": "^11.0", + "phpunit/phpunit": "^12.0", "vimeo/psalm": "^6.0" }, "suggest": { @@ -6812,7 +6722,7 @@ ], "support": { "issues": "https://github.com/maennchen/ZipStream-PHP/issues", - "source": "https://github.com/maennchen/ZipStream-PHP/tree/3.1.2" + "source": "https://github.com/maennchen/ZipStream-PHP/tree/3.2.0" }, "funding": [ { @@ -6820,7 +6730,7 @@ "type": "github" } ], - "time": "2025-01-27T12:07:53+00:00" + "time": "2025-07-17T11:15:13+00:00" }, { "name": "markbaker/complex", @@ -7900,16 +7810,16 @@ }, { "name": "php-mock/php-mock-phpunit", - "version": "2.13.0", + "version": "2.13.1", "source": { "type": "git", "url": "https://github.com/php-mock/php-mock-phpunit.git", - "reference": "498e5e25ee7824570332581304c2bb7e37d75e80" + "reference": "29f90fe44a04105959d6ae835b10c9e0da2fcaa7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-mock/php-mock-phpunit/zipball/498e5e25ee7824570332581304c2bb7e37d75e80", - "reference": "498e5e25ee7824570332581304c2bb7e37d75e80", + "url": "https://api.github.com/repos/php-mock/php-mock-phpunit/zipball/29f90fe44a04105959d6ae835b10c9e0da2fcaa7", + "reference": "29f90fe44a04105959d6ae835b10c9e0da2fcaa7", "shasum": "" }, "require": { @@ -7956,7 +7866,7 @@ ], "support": { "issues": "https://github.com/php-mock/php-mock-phpunit/issues", - "source": "https://github.com/php-mock/php-mock-phpunit/tree/2.13.0" + "source": "https://github.com/php-mock/php-mock-phpunit/tree/2.13.1" }, "funding": [ { @@ -7964,7 +7874,7 @@ "type": "github" } ], - "time": "2025-03-19T20:58:51+00:00" + "time": "2025-09-23T06:00:08+00:00" }, { "name": "phpoffice/phpspreadsheet", @@ -8074,16 +7984,16 @@ }, { "name": "phpunit/php-code-coverage", - "version": "11.0.10", + "version": "11.0.11", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "1a800a7446add2d79cc6b3c01c45381810367d76" + "reference": "4f7722aa9a7b76aa775e2d9d4e95d1ea16eeeef4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/1a800a7446add2d79cc6b3c01c45381810367d76", - "reference": "1a800a7446add2d79cc6b3c01c45381810367d76", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/4f7722aa9a7b76aa775e2d9d4e95d1ea16eeeef4", + "reference": "4f7722aa9a7b76aa775e2d9d4e95d1ea16eeeef4", "shasum": "" }, "require": { @@ -8140,7 +8050,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/show" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.11" }, "funding": [ { @@ -8160,7 +8070,7 @@ "type": "tidelift" } ], - "time": "2025-06-18T08:56:18+00:00" + "time": "2025-08-27T14:37:49+00:00" }, { "name": "phpunit/php-file-iterator", @@ -8409,16 +8319,16 @@ }, { "name": "phpunit/phpunit", - "version": "11.5.34", + "version": "11.5.42", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "3e4c6ef395f7cb61a6206c23e0e04b31724174f2" + "reference": "1c6cb5dfe412af3d0dfd414cfd110e3b9cfdbc3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3e4c6ef395f7cb61a6206c23e0e04b31724174f2", - "reference": "3e4c6ef395f7cb61a6206c23e0e04b31724174f2", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/1c6cb5dfe412af3d0dfd414cfd110e3b9cfdbc3c", + "reference": "1c6cb5dfe412af3d0dfd414cfd110e3b9cfdbc3c", "shasum": "" }, "require": { @@ -8432,7 +8342,7 @@ "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=8.2", - "phpunit/php-code-coverage": "^11.0.10", + "phpunit/php-code-coverage": "^11.0.11", "phpunit/php-file-iterator": "^5.1.0", "phpunit/php-invoker": "^5.0.1", "phpunit/php-text-template": "^4.0.1", @@ -8442,7 +8352,7 @@ "sebastian/comparator": "^6.3.2", "sebastian/diff": "^6.0.2", "sebastian/environment": "^7.2.1", - "sebastian/exporter": "^6.3.0", + "sebastian/exporter": "^6.3.2", "sebastian/global-state": "^7.0.2", "sebastian/object-enumerator": "^6.0.1", "sebastian/type": "^5.1.3", @@ -8490,7 +8400,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.34" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.42" }, "funding": [ { @@ -8514,7 +8424,7 @@ "type": "tidelift" } ], - "time": "2025-08-20T14:41:45+00:00" + "time": "2025-09-28T12:09:13+00:00" }, { "name": "psr/cache", @@ -8643,39 +8553,108 @@ }, "time": "2025-08-04T12:39:37+00:00" }, + { + "name": "riverline/multipart-parser", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/Riverline/multipart-parser.git", + "reference": "1410f23a8fd416a0cf5c8867ea9c95544016c831" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Riverline/multipart-parser/zipball/1410f23a8fd416a0cf5c8867ea9c95544016c831", + "reference": "1410f23a8fd416a0cf5c8867ea9c95544016c831", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=5.6.0" + }, + "require-dev": { + "laminas/laminas-diactoros": "^1.8.7 || ^2.11.1", + "phpunit/phpunit": "^5.7 || ^9.0", + "psr/http-message": "^1.0", + "symfony/psr-http-message-bridge": "^1.1 || ^2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Riverline\\MultiPartParser\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Romain Cambien", + "email": "romain@cambien.net" + }, + { + "name": "Riverline", + "homepage": "http://www.riverline.fr" + } + ], + "description": "One class library to parse multipart content with encoding and charset support.", + "keywords": [ + "http", + "multipart", + "parser" + ], + "support": { + "issues": "https://github.com/Riverline/multipart-parser/issues", + "source": "https://github.com/Riverline/multipart-parser/tree/2.2.0" + }, + "time": "2025-04-29T08:38:14+00:00" + }, { "name": "ronasit/laravel-helpers", - "version": "2.7.1", + "version": "3.5.8", "source": { "type": "git", "url": "https://github.com/RonasIT/laravel-helpers.git", - "reference": "4f9c108744b06a67b3a4d5dea81ccfbb023d83f7" + "reference": "b2ef76134afd66dedb5a956712f1bd1b483ef9c1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/RonasIT/laravel-helpers/zipball/4f9c108744b06a67b3a4d5dea81ccfbb023d83f7", - "reference": "4f9c108744b06a67b3a4d5dea81ccfbb023d83f7", + "url": "https://api.github.com/repos/RonasIT/laravel-helpers/zipball/b2ef76134afd66dedb5a956712f1bd1b483ef9c1", + "reference": "b2ef76134afd66dedb5a956712f1bd1b483ef9c1", "shasum": "" }, "require": { - "doctrine/dbal": "^3.6", + "doctrine/dbal": "^4.2", "ext-json": "*", - "guzzlehttp/guzzle": ">=6.0", - "laravel/framework": ">=5.5.0", - "maatwebsite/excel": "3.*", - "php": ">=7.4", - "php-mock/php-mock-phpunit": "^2.9" + "guzzlehttp/guzzle": "^7.9.2", + "laravel/framework": ">=11.20", + "maatwebsite/excel": "^3.1.55", + "php": ">=8.3", + "php-mock/php-mock-phpunit": "^2.10", + "riverline/multipart-parser": "^2.1" }, "require-dev": { - "fakerphp/faker": "^1.23", - "mockery/mockery": "1.*", - "mpyw/laravel-database-mock": "0.0.1-alpha4", - "orchestra/testbench": "^6.25", - "php-coveralls/php-coveralls": "^2.5", - "phpunit/phpunit": "9.*" + "brainmaestro/composer-git-hooks": "^3.0", + "fakerphp/faker": "^1.23.1", + "friendsofphp/php-cs-fixer": "^3.62", + "mockery/mockery": "^1.6.12", + "mpyw/laravel-database-mock": "^0.0.1-alpha5", + "orchestra/testbench": "^9.3", + "php-coveralls/php-coveralls": "^2.7", + "phpunit/phpunit": "^10.5.30" }, "type": "library", "extra": { + "hooks": { + "config": { + "stop-on-failure": [ + "pre-commit" + ] + }, + "pre-commit": [ + "docker-compose up -d php && docker-compose exec -T php vendor/bin/php-cs-fixer fix && docker-compose exec -T php vendor/bin/tlint" + ] + }, "laravel": { "providers": [ "RonasIT\\Support\\HelpersServiceProvider" @@ -8687,9 +8666,7 @@ "src/helpers.php" ], "psr-4": { - "RonasIT\\Support\\": "src/", - "RonasIT\\Support\\Tests\\": "tests/", - "RonasIT\\Support\\Tests\\Support\\": "tests/support/" + "RonasIT\\Support\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -8709,9 +8686,9 @@ ], "support": { "issues": "https://github.com/RonasIT/laravel-helpers/issues", - "source": "https://github.com/RonasIT/laravel-helpers/tree/2.7.1" + "source": "https://github.com/RonasIT/laravel-helpers/tree/3.5.8" }, - "time": "2025-02-24T13:45:41+00:00" + "time": "2025-10-08T08:37:24+00:00" }, { "name": "sebastian/cli-parser", @@ -9178,16 +9155,16 @@ }, { "name": "sebastian/exporter", - "version": "6.3.0", + "version": "6.3.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "3473f61172093b2da7de1fb5782e1f24cc036dc3" + "reference": "70a298763b40b213ec087c51c739efcaa90bcd74" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/3473f61172093b2da7de1fb5782e1f24cc036dc3", - "reference": "3473f61172093b2da7de1fb5782e1f24cc036dc3", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/70a298763b40b213ec087c51c739efcaa90bcd74", + "reference": "70a298763b40b213ec087c51c739efcaa90bcd74", "shasum": "" }, "require": { @@ -9201,7 +9178,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "6.1-dev" + "dev-main": "6.3-dev" } }, "autoload": { @@ -9244,15 +9221,27 @@ "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", "security": "https://github.com/sebastianbergmann/exporter/security/policy", - "source": "https://github.com/sebastianbergmann/exporter/tree/6.3.0" + "source": "https://github.com/sebastianbergmann/exporter/tree/6.3.2" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", + "type": "tidelift" } ], - "time": "2024-12-05T09:17:50+00:00" + "time": "2025-09-24T06:12:51+00:00" }, { "name": "sebastian/global-state", @@ -9872,7 +9861,7 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": "^8.2" + "php": "^8.3" }, "platform-dev": [], "plugin-api-version": "2.6.0" diff --git a/docker-compose.yml b/docker-compose.yml index 1abb6d05..8ea9f7f6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,6 @@ services: nginx: - image: ghcr.io/ronasit/php-nginx-dev:8.2 + image: ghcr.io/ronasit/php-nginx-dev:8.3 working_dir: /app ports: - 80:80 diff --git a/migrations/create_exponent_push_notification_interests_table.php.stub b/migrations/create_exponent_push_notification_interests_table.php.stub index b705c1d4..e4830c89 100644 --- a/migrations/create_exponent_push_notification_interests_table.php.stub +++ b/migrations/create_exponent_push_notification_interests_table.php.stub @@ -12,10 +12,11 @@ class CreateExponentPushNotificationInterestsTable extends Migration public function up() { Schema::create(config('exponent-push-notifications.interests.database.table_name'), function (Blueprint $table) { + $table->increments('id'); $table->string('key')->index(); $table->string('value'); - $table->unique(['key','value']); + $table->unique(['key', 'value']); }); } @@ -26,4 +27,4 @@ class CreateExponentPushNotificationInterestsTable extends Migration { Schema::drop(config('exponent-push-notifications.interests.database.table_name')); } -} \ No newline at end of file +} diff --git a/src/Http/ExpoController.php b/src/Http/ExpoController.php index 0413dde0..40f56b14 100644 --- a/src/Http/ExpoController.php +++ b/src/Http/ExpoController.php @@ -2,12 +2,11 @@ namespace NotificationChannels\ExpoPushNotifications\Http; -use Illuminate\Http\JsonResponse; -use Illuminate\Http\Request; use Illuminate\Routing\Controller; -use Illuminate\Support\Facades\Auth; -use Illuminate\Support\Facades\Validator; use NotificationChannels\ExpoPushNotifications\ExpoChannel; +use NotificationChannels\ExpoPushNotifications\Http\Requests\SubscribeRequest; +use NotificationChannels\ExpoPushNotifications\Http\Requests\UnsubscribeRequest; +use Illuminate\Http\Response; class ExpoController extends Controller { @@ -16,72 +15,31 @@ public function __construct( ) { } - public function subscribe(Request $request): JsonResponse + public function subscribe(SubscribeRequest $request): Response { - $validator = Validator::make($request->all(), [ - 'expo_token' => 'required|string', - ]); + $interest = $this + ->expoChannel + ->interestName($request->user()); - if ($validator->fails()) { - return new JsonResponse([ - 'status' => 'failed', - 'error' => [ - 'message' => 'Expo Token is required', - ], - ], 422); - } + $this + ->expoChannel + ->expo + ->subscribe($interest, $request->validated('expo_token')); - $token = $request->get('expo_token'); - - $interest = $this->expoChannel->interestName(Auth::user()); - - try { - $this->expoChannel->expo->subscribe($interest, $token); - } catch (\Exception $e) { - return new JsonResponse([ - 'status' => 'failed', - 'error' => [ - 'message' => $e->getMessage(), - ], - ], 500); - } - - return new JsonResponse([ - 'status' => 'succeeded', - 'expo_token' => $token, - ], 200); + return response()->noContent(); } - public function unsubscribe(Request $request): JsonResponse + public function unsubscribe(UnsubscribeRequest $request): Response { - $interest = $this->expoChannel->interestName(Auth::user()); - - $validator = Validator::make($request->all(), [ - 'expo_token' => 'sometimes|string', - ]); - - if ($validator->fails()) { - return new JsonResponse([ - 'status' => 'failed', - 'error' => [ - 'message' => 'Expo Token is invalid', - ], - ], 422); - } - - $token = $request->get('expo_token') ?: null; + $interest = $this + ->expoChannel + ->interestName($request->user()); - try { - $deleted = $this->expoChannel->expo->unsubscribe($interest, $token); - } catch (\Exception $e) { - return new JsonResponse([ - 'status' => 'failed', - 'error' => [ - 'message' => $e->getMessage(), - ], - ], 500); - } + $this + ->expoChannel + ->expo + ->unsubscribe($interest, $request->validated('expo_token')); - return new JsonResponse(['deleted' => $deleted]); + return response()->noContent(); } } diff --git a/src/Http/Requests/SubscribeRequest.php b/src/Http/Requests/SubscribeRequest.php new file mode 100644 index 00000000..44ecc5eb --- /dev/null +++ b/src/Http/Requests/SubscribeRequest.php @@ -0,0 +1,15 @@ + 'required|string', + ]; + } +} diff --git a/src/Http/Requests/UnsubscribeRequest.php b/src/Http/Requests/UnsubscribeRequest.php new file mode 100644 index 00000000..327c5862 --- /dev/null +++ b/src/Http/Requests/UnsubscribeRequest.php @@ -0,0 +1,15 @@ + 'sometimes|string', + ]; + } +} diff --git a/tests/ExpoControllerTest.php b/tests/ExpoControllerTest.php index 78c22a7e..73102b35 100644 --- a/tests/ExpoControllerTest.php +++ b/tests/ExpoControllerTest.php @@ -2,279 +2,94 @@ namespace NotificationChannels\ExpoPushNotifications\Test; -use ExponentPhpSDK\Expo; -use ExponentPhpSDK\ExpoRegistrar; -use ExponentPhpSDK\ExpoRepository; use ExponentPhpSDK\Repositories\ExpoFileDriver; -use Illuminate\Contracts\Validation\Factory; -use Illuminate\Events\Dispatcher; -use Illuminate\Http\Request; -use Illuminate\Support\Facades\Auth; -use Illuminate\Support\Facades\Validator; -use NotificationChannels\ExpoPushNotifications\ExpoChannel; -use NotificationChannels\ExpoPushNotifications\Http\ExpoController; -use NotificationChannels\ExpoPushNotifications\Models\Interest; use NotificationChannels\ExpoPushNotifications\Repositories\ExpoDatabaseDriver; +use NotificationChannels\ExpoPushNotifications\Test\database\Models\User; +use NotificationChannels\ExpoPushNotifications\Models\Interest; +use NotificationChannels\ExpoPushNotifications\Test\Support\ModelTestState; +use PHPUnit\Framework\Attributes\DataProvider; +use ExponentPhpSDK\ExpoRepository; class ExpoControllerTest extends TestCase { - /** - * @var ExpoController - */ - protected $expoController; + protected static User $user; + protected static User $secondUser; - /** - * Sets up the expo controller with the given expo channel. - * - * @param ExpoRepository $expoRepository - * @return array - */ - protected function setupExpo(ExpoRepository $expoRepository) - { - $expoChannel = new ExpoChannel(new Expo(new ExpoRegistrar($expoRepository)), new Dispatcher); - $expoController = new ExpoController($expoChannel); - - return [$expoController, $expoChannel]; - } + protected static ModelTestState $interestTestState; protected function setUp(): void { parent::setUp(); - $this->setUpDatabase(); + self::$user ??= User::find(1); + self::$secondUser ??= User::find(2); - // We will fake an authenticated user - Auth::shouldReceive('user')->andReturn(new User()); + self::$interestTestState ??= new ModelTestState(Interest::class); } - protected function tearDown(): void - { - \Mockery::close(); - - parent::tearDown(); - } - - /** - * Data provider to help test the expo controller with the different repositories. - * - * @return array - */ - public static function availableRepositories() + public static function getExpoDriver(): array { return [ - [new ExpoDatabaseDriver], - [new ExpoFileDriver], + ['driver' => new ExpoDatabaseDriver()], + ['driver' => new ExpoFileDriver()], ]; } - /** - * @test - * - * @param $expoRepository - * - * @dataProvider availableRepositories - */ - public function aDeviceCanSubscribeToTheSystem($expoRepository) + #[DataProvider('getExpoDriver')] + public function testSubscribe(ExpoRepository $driver): void { - [$expoController, $expoChannel] = $this->setupExpo($expoRepository); - - // We will fake a request with the following data - $data = ['expo_token' => 'ExponentPushToken[fakeToken]']; - $request = $this->mockRequest($data); - $request->shouldReceive('get')->with('expo_token')->andReturn($data['expo_token']); - - $this->mockValidator(false); + $this->bindExpoRepository($driver); - /** @var Request $request */ - $response = $expoController->subscribe($request); - $response = json_decode($response->content()); + $response = $this->actingAs(self::$user)->json('post', 'exponent/devices/subscribe', [ + 'expo_token' => 'ExponentPushToken[fakeToken]', + ]); - // The response should contain a succeeded status - $this->assertEquals('succeeded', $response->status); - // The response should return the registered token - $this->assertEquals($data['expo_token'], $response->expo_token); + $response->assertNoContent(); - if ($expoRepository instanceof ExpoDatabaseDriver) { - $this->assertDatabaseHas(config('exponent-push-notifications.interests.database.table_name'), [ - 'key' => 'NotificationChannels.ExpoPushNotifications.Test.User.'.(new User)->getKey(), - 'value' => $data['expo_token'], - ]); + if ($driver instanceof ExpoDatabaseDriver) { + self::$interestTestState->assertChangesEqualsFixture('subscribe'); } } - /** - * @test - * - * @param $expoRepository - * - * @dataProvider availableRepositories - */ - public function subscribeReturnsErrorResponseIfTokenInvalid($expoRepository) - { - [$expoController, $expoChannel] = $this->setupExpo($expoRepository); - - // We will fake a request with no data - $request = $this->mockRequest([]); - - $this->mockValidator(true); - - /** @var Request $request */ - $response = $expoController->subscribe($request); - - // The response should contain a failed status - $this->assertEquals('failed', json_decode($response->content())->status); - // The response status should be 422 - $this->assertEquals(422, $response->getStatusCode()); - } - - /** @test */ - public function subscribeReturnsErrorResponseIfExceptionIsThrown() - { - // We will fake a request with the following data - $data = ['expo_token' => 'ExponentPushToken[fakeToken]']; - $request = $this->mockRequest($data); - $request->shouldReceive('get')->andReturn($data['expo_token']); - - $this->mockValidator(false); - - $expo = \Mockery::mock(Expo::class); - $expo->shouldReceive('subscribe')->andThrow(\Exception::class); - - /** @var Expo $expo */ - $expoChannel = new ExpoChannel($expo, new Dispatcher()); - - /** @var Request $request */ - $response = (new ExpoController($expoChannel))->subscribe($request); - $response = json_decode($response->content()); - - $this->assertEquals('failed', $response->status); - } - - /** - * @test - * - * - * @dataProvider availableRepositories - * - * @param $expoRepository - */ - public function aDeviceCanUnsubscribeSingleTokenFromTheSystem($expoRepository) + public function testSubscribeInvalidToken(): void { - [$expoController, $expoChannel] = $this->setupExpo($expoRepository); - - // We will fake a request with the following data - $data = ['expo_token' => 'ExponentPushToken[fakeToken]']; - $request = $this->mockRequest($data); - $request->shouldReceive('get')->with('expo_token')->andReturn($data['expo_token']); - - $this->mockValidator(false); + $response = $this->actingAs(self::$user)->json('post', 'exponent/devices/subscribe', [ + 'expo_token' => null, + ]); - // We will subscribe an interest to the server. - $token = 'ExponentPushToken[fakeToken]'; - $interest = $expoChannel->interestName(new User()); - $expoChannel->expo->subscribe($interest, $token); + $response->assertUnprocessable(); - $response = $expoController->unsubscribe($request); - $response = json_decode($response->content()); + $response->assertJson(['message' => 'The expo token field is required.']); - // The response should contain a deleted property with value true - $this->assertTrue($response->deleted); - - if ($expoRepository instanceof ExpoDatabaseDriver) { - $this->assertDatabaseMissing(config('exponent-push-notifications.interests.database.table_name'), [ - 'key' => 'NotificationChannels.ExpoPushNotifications.Test.User.'.(new User)->getKey(), - 'value' => $data['expo_token'], - ]); - } + self::$interestTestState->assertNotChanged(); } - /** - * @test - * - * @param $expoRepository - * - * @dataProvider availableRepositories - */ - public function aDeviceCanUnsubscribeFromTheSystem($expoRepository) + #[DataProvider('getExpoDriver')] + public function testUnsubscribe(ExpoRepository $driver): void { - [$expoController, $expoChannel] = $this->setupExpo($expoRepository); - - // We will fake a request with the following data - $request = $this->mockRequest([]); - $request->shouldReceive('get')->with('expo_token')->andReturn([]); + $this->bindExpoRepository($driver); - $this->mockValidator(false); + $response = $this->actingAs(self::$secondUser)->json('post', 'exponent/devices/unsubscribe', [ + 'expo_token' => 'ExponentPushToken[2]', + ]); - // We will subscribe an interest to the server. - $token = 'ExponentPushToken[fakeToken]'; - $interest = $expoChannel->interestName(new User()); - $expoChannel->expo->subscribe($interest, $token); + $response->assertNoContent(); - $response = $expoController->unsubscribe($request); - $response = json_decode($response->content()); - - // The response should contain a deleted property with value true - $this->assertTrue($response->deleted); - - if ($expoRepository instanceof ExpoDatabaseDriver) { - $this->assertEquals(0, Interest::count()); + if ($driver instanceof ExpoDatabaseDriver) { + self::$interestTestState->assertChangesEqualsFixture('unsubscribe'); } } - /** @test */ - public function unsubscribeReturnsErrorResponseIfExceptionIsThrown() - { - $request = $this->mockRequest([]); - $request->shouldReceive('get')->with('expo_token')->andReturn([]); - - $expo = \Mockery::mock(Expo::class); - $expo->shouldReceive('unsubscribe')->andThrow(\Exception::class); - - /** @var Expo $expo */ - $response = (new ExpoController(new ExpoChannel($expo, new Dispatcher())))->unsubscribe($request); - $response = json_decode($response->content()); - - $this->assertEquals('failed', $response->status); - } - - /** - * Mocks a request for the ExpoController. - * - * @param $data - * @return \Mockery\MockInterface - */ - public function mockRequest($data) + public function testUnsubscribeInvalidToken(): void { - $request = \Mockery::mock(Request::class); - $request->shouldReceive('all')->andReturn($data); - - return $request; - } - - /** - * @param bool $fails - * @return \Mockery\MockInterface - */ - public function mockValidator(bool $fails) - { - $validator = \Mockery::mock(\Illuminate\Validation\Validator::class); - - $validation = \Mockery::mock(Factory::class); + $response = $this->actingAs(self::$secondUser)->json('post', 'exponent/devices/unsubscribe', [ + 'expo_token' => null, + ]); - $validation->shouldReceive('make')->once()->andReturn($validator); + $response->assertUnprocessable(); - $validator->shouldReceive('fails')->once()->andReturn($fails); + $response->assertJson(['message' => 'The expo token field must be a string.']); - Validator::swap($validation); - - return $validator; - } -} - -class User -{ - public function getKey() - { - return 1; + self::$interestTestState->assertNotChanged(); } } diff --git a/tests/Support/ModelTestState.php b/tests/Support/ModelTestState.php new file mode 100644 index 00000000..9c1fb20d --- /dev/null +++ b/tests/Support/ModelTestState.php @@ -0,0 +1,15 @@ +set('database.default', 'sqlite'); + $this->loadMigrationsFrom(__DIR__ . '/database/migrations'); + $this->loadTestDump(); - $app['config']->set('database.connections.sqlite', [ - 'driver' => 'sqlite', - 'database' => $this->getDatabaseDirectory().'/database.sqlite', - 'prefix' => '', - ]); - - $app['config']->set('auth.providers.users.model', User::class); + if (config('database.default') === 'pgsql') { + $this->prepareSequences(); + } } - /** - * Sets up the database. - * - * @return void - */ - protected function setUpDatabase() + protected function getPackageProviders($app): array { - $this->resetDatabase(); - - $this->createExponentPushNotificationInterestsTable(); - } - - /** - * Drops the database. - * - * @return void - */ - protected function resetDatabase() - { - file_put_contents(__DIR__.'/temp'.'/database.sqlite', null); + return [ + ExpoPushNotificationsServiceProvider::class, + ]; } - /** - * Creates the interests table. - * - * @return void - */ - protected function createExponentPushNotificationInterestsTable() + public function getEnvironmentSetUp($app): void { - include_once '__DIR__'.'/../migrations/create_exponent_push_notification_interests_table.php.stub'; + $this->setupDb($app); - (new \CreateExponentPushNotificationInterestsTable())->up(); + $app['config']->set('auth.providers.users.model', User::class); + $app['config']->set('exponent-push-notifications.middleware', []); } - /** - * Gets the directory path for the testing database. - * - * @return string - */ - public function getDatabaseDirectory(): string + protected function setupDb($app): void { - return __DIR__.'/temp'; + $app['config']->set('database.default', env('DB_CONNECTION', 'pgsql')); + $app['config']->set('database.connections.pgsql', [ + 'driver' => env('DB_DRIVER', 'pgsql'), + 'host' => env('DB_HOST', 'pgsql'), + 'database' => env('DB_DATABASE', 'forge'), + 'username' => env('DB_USERNAME', 'forge'), + 'password' => env('DB_PASSWORD', 'secret'), + ]); } public function getFixturePath(string $fixtureName): string @@ -93,4 +60,14 @@ public function getFixturePath(string $fixtureName): string return getcwd() . "/tests/fixtures/{$className}/{$fixtureName}"; } + + protected function bindExpoRepository(ExpoRepository $driver): void + { + $this->app->bind(ExpoRepository::class, fn () => $driver); + + $this->app->bind(ExpoChannel::class, fn ($app) => new ExpoChannel( + expo: new Expo(new ExpoRegistrar($driver)), + events: $app['events'] + )); + } } diff --git a/tests/database/Models/User.php b/tests/database/Models/User.php new file mode 100644 index 00000000..0ee017fc --- /dev/null +++ b/tests/database/Models/User.php @@ -0,0 +1,21 @@ +increments('id'); + $table->string('key')->index(); + $table->string('value'); + + $table->unique(['key', 'value']); + }); + } + + public function down(): void + { + Schema::dropIfExists('exponent_push_notification_interests'); + } +} diff --git a/tests/database/migrations/2014_10_12_000000_create_users_table.php b/tests/database/migrations/2014_10_12_000000_create_users_table.php new file mode 100644 index 00000000..420f3d31 --- /dev/null +++ b/tests/database/migrations/2014_10_12_000000_create_users_table.php @@ -0,0 +1,22 @@ +increments('id'); + $table->string('email')->unique(); + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::dropIfExists('users'); + } +} diff --git a/tests/fixtures/ExpoControllerTest/db_changes/exponent_push_notification_interests/subscribe.json b/tests/fixtures/ExpoControllerTest/db_changes/exponent_push_notification_interests/subscribe.json new file mode 100644 index 00000000..3ead1d74 --- /dev/null +++ b/tests/fixtures/ExpoControllerTest/db_changes/exponent_push_notification_interests/subscribe.json @@ -0,0 +1,11 @@ +{ + "updated": [], + "created": [ + { + "id": 2, + "key": "NotificationChannels.ExpoPushNotifications.Test.database.Models.User.1", + "value": "ExponentPushToken[fakeToken]" + } + ], + "deleted": [] +} \ No newline at end of file diff --git a/tests/fixtures/ExpoControllerTest/db_changes/exponent_push_notification_interests/unsubscribe.json b/tests/fixtures/ExpoControllerTest/db_changes/exponent_push_notification_interests/unsubscribe.json new file mode 100644 index 00000000..b1fec8e4 --- /dev/null +++ b/tests/fixtures/ExpoControllerTest/db_changes/exponent_push_notification_interests/unsubscribe.json @@ -0,0 +1,11 @@ +{ + "updated": [], + "created": [], + "deleted": [ + { + "id": 1, + "key": "NotificationChannels.ExpoPushNotifications.Test.database.Models.User.2", + "value": "ExponentPushToken[2]" + } + ] +} \ No newline at end of file diff --git a/tests/fixtures/ExpoControllerTest/dump.sql b/tests/fixtures/ExpoControllerTest/dump.sql new file mode 100644 index 00000000..cc2fa936 --- /dev/null +++ b/tests/fixtures/ExpoControllerTest/dump.sql @@ -0,0 +1,6 @@ +INSERT INTO users(id, email) VALUES + (1, 'test@email.com'), + (2, 'another_test@email.com'); + +INSERT INTO exponent_push_notification_interests(id, key, value) VALUES + (1, 'NotificationChannels.ExpoPushNotifications.Test.database.Models.User.2', 'ExponentPushToken[2]');