WIP: feat: DTF's Goteo v4 API #1
@@ -17,7 +17,10 @@
|
||||
###> symfony/framework-bundle ###
|
||||
APP_ENV=dev
|
||||
APP_SECRET=b8ec44a08e5f4ba917879713a4a8e63c
|
||||
APP_LOCALE='es'
|
||||
APP_LOCALE='en'
|
||||
# DO NOT LEAVE OPEN UNLESS YOU'VE GUARANTEED THAT TRAFFIC WILL ONLY COME FROM TRUSTED ORIGINS
|
||||
# https://symfony.com/doc/6.4/deployment/proxies.html
|
||||
APP_TRUSTED_PROXIES='127.0.0.1,REMOTE_ADDR'
|
||||
###< symfony/framework-bundle ###
|
||||
|
||||
###> doctrine/doctrine-bundle ###
|
||||
@@ -69,6 +72,7 @@ OAUTH_PRIVATE_KEY=%kernel.project_dir%/config/jwt/private.pem
|
||||
OAUTH_PUBLIC_KEY=%kernel.project_dir%/config/jwt/public.pem
|
||||
OAUTH_PASSPHRASE=df7ec1f514c44825926e3dabaf34e3f5
|
||||
OAUTH_ENCRYPTION_KEY=325b6ce98c009bb83e885657ec08d142
|
||||
OAUTH_ACCESS_TOKEN_TTL=PT1H
|
||||
###< league/oauth2-server-bundle ###
|
||||
|
||||
###> knpuniversity/oauth2-client-bundle ###
|
||||
|
||||
@@ -34,11 +34,13 @@ jobs:
|
||||
php_version: ${{ env.PHP_VERSION }}
|
||||
php_extensions: ${{ env.PHP_EXTENSIONS }}
|
||||
|
||||
- name: PHP diagnostics
|
||||
run: |
|
||||
php -v
|
||||
php --ini
|
||||
php -r 'echo "PHP_VERSION=", PHP_VERSION, PHP_EOL;'
|
||||
|
||||
- name: PHPUnit
|
||||
uses: php-actions/phpunit@v4
|
||||
run: php -d memory_limit=128M bin/phpunit -c phpunit.xml.dist
|
||||
env:
|
||||
DATABASE_URL: mysql://root:goteo@127.0.0.1:${{ job.services.mariadb.ports['3306'] }}/goteo
|
||||
with:
|
||||
php_version: ${{ env.PHP_VERSION }}
|
||||
php_extensions: ${{ env.PHP_EXTENSIONS }}
|
||||
configuration: phpunit.xml.dist
|
||||
|
||||
Generated
+153
-154
@@ -3916,16 +3916,16 @@
|
||||
},
|
||||
{
|
||||
"name": "league/oauth2-server-bundle",
|
||||
"version": "v1.1.1",
|
||||
"version": "v1.2.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/thephpleague/oauth2-server-bundle.git",
|
||||
"reference": "b0094bef19cb0dfa526ea9eec6d8a863b91a3f7d"
|
||||
"reference": "db8d6a267b3bb76451816751539d50aa56588247"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/thephpleague/oauth2-server-bundle/zipball/b0094bef19cb0dfa526ea9eec6d8a863b91a3f7d",
|
||||
"reference": "b0094bef19cb0dfa526ea9eec6d8a863b91a3f7d",
|
||||
"url": "https://api.github.com/repos/thephpleague/oauth2-server-bundle/zipball/db8d6a267b3bb76451816751539d50aa56588247",
|
||||
"reference": "db8d6a267b3bb76451816751539d50aa56588247",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -3934,16 +3934,18 @@
|
||||
"nyholm/psr7": "^1.4",
|
||||
"php": "^8.1",
|
||||
"psr/http-factory": "^1.0",
|
||||
"symfony/deprecation-contracts": "^3",
|
||||
"symfony/event-dispatcher": "^6.4|^7.0|^8.0",
|
||||
"symfony/filesystem": "^6.4|^7.0|^8.0",
|
||||
"symfony/framework-bundle": "^6.4|^7.0|^8.0",
|
||||
"symfony/psr-http-message-bridge": "^6.4|^7.0|^8.0",
|
||||
"symfony/security-bundle": "^6.4|^7.0|^8.0"
|
||||
"symfony/deprecation-contracts": "^3.0",
|
||||
"symfony/event-dispatcher": "^6.4|^7.4|^8.0",
|
||||
"symfony/filesystem": "^6.4|^7.4|^8.0",
|
||||
"symfony/framework-bundle": "^6.4|^7.4|^8.0",
|
||||
"symfony/password-hasher": "^6.4|^7.4|^8.0",
|
||||
"symfony/psr-http-message-bridge": "^6.4|^7.4|^8.0",
|
||||
"symfony/security-bundle": "^6.4|^7.4|^8.0"
|
||||
},
|
||||
"conflict": {
|
||||
"doctrine/doctrine-bundle": "<2.8.0",
|
||||
"doctrine/orm": "<2.14"
|
||||
"doctrine/orm": "<2.14",
|
||||
"symfony/console": "<6.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/doctrine-bundle": "^2.8|^3.0",
|
||||
@@ -3953,19 +3955,15 @@
|
||||
"php-cs-fixer/shim": "^3.38",
|
||||
"phpstan/phpstan": "^2.1",
|
||||
"phpstan/phpstan-symfony": "^2.0",
|
||||
"symfony/browser-kit": "^6.4|^7.0|^8.0",
|
||||
"symfony/phpunit-bridge": "^7.3"
|
||||
"rector/rector": "^2.5",
|
||||
"symfony/browser-kit": "^6.4|^7.4|^8.0",
|
||||
"symfony/phpunit-bridge": "^8.1"
|
||||
},
|
||||
"suggest": {
|
||||
"doctrine/doctrine-bundle": "Allow usage of doctrine as persistence layer",
|
||||
"doctrine/orm": "Allow usage of doctrine as persistence layer"
|
||||
},
|
||||
"type": "symfony-bundle",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"League\\Bundle\\OAuth2ServerBundle\\": "src/"
|
||||
@@ -3996,9 +3994,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/thephpleague/oauth2-server-bundle/issues",
|
||||
"source": "https://github.com/thephpleague/oauth2-server-bundle/tree/v1.1.1"
|
||||
"source": "https://github.com/thephpleague/oauth2-server-bundle/tree/v1.2.2"
|
||||
},
|
||||
"time": "2026-02-06T11:56:53+00:00"
|
||||
"time": "2026-07-26T12:46:32+00:00"
|
||||
},
|
||||
{
|
||||
"name": "league/uri",
|
||||
@@ -5934,16 +5932,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/cache",
|
||||
"version": "v6.4.43",
|
||||
"version": "v6.4.46",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/cache.git",
|
||||
"reference": "8f0b5de5d9f5131aa99fdab78d7802efef9322dc"
|
||||
"reference": "b001ef092224de1358462ceabfbdf4d4c2d4c9d2"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/cache/zipball/8f0b5de5d9f5131aa99fdab78d7802efef9322dc",
|
||||
"reference": "8f0b5de5d9f5131aa99fdab78d7802efef9322dc",
|
||||
"url": "https://api.github.com/repos/symfony/cache/zipball/b001ef092224de1358462ceabfbdf4d4c2d4c9d2",
|
||||
"reference": "b001ef092224de1358462ceabfbdf4d4c2d4c9d2",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -5966,7 +5964,7 @@
|
||||
"symfony/cache-implementation": "1.1|2.0|3.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"cache/integration-tests": "dev-master",
|
||||
"cache/integration-tests": "^0.18",
|
||||
"doctrine/dbal": "^2.13.1|^3|^4",
|
||||
"predis/predis": "^1.1|^2.0",
|
||||
"psr/simple-cache": "^1.0|^2.0|^3.0",
|
||||
@@ -6010,7 +6008,7 @@
|
||||
"psr6"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/cache/tree/v6.4.43"
|
||||
"source": "https://github.com/symfony/cache/tree/v6.4.46"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -6030,7 +6028,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-07-28T21:33:22+00:00"
|
||||
"time": "2026-09-08T06:25:25+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/cache-contracts",
|
||||
@@ -6192,16 +6190,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/config",
|
||||
"version": "v6.4.43",
|
||||
"version": "v6.4.44",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/config.git",
|
||||
"reference": "e39cdd91b2adf67a117bea29660b73d896937d86"
|
||||
"reference": "6a5abf67fd5b138df96984fe6f8fd736fb7d6b29"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/config/zipball/e39cdd91b2adf67a117bea29660b73d896937d86",
|
||||
"reference": "e39cdd91b2adf67a117bea29660b73d896937d86",
|
||||
"url": "https://api.github.com/repos/symfony/config/zipball/6a5abf67fd5b138df96984fe6f8fd736fb7d6b29",
|
||||
"reference": "6a5abf67fd5b138df96984fe6f8fd736fb7d6b29",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -6247,7 +6245,7 @@
|
||||
"description": "Helps you find, load, combine, autofill and validate configuration values of any kind",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/config/tree/v6.4.43"
|
||||
"source": "https://github.com/symfony/config/tree/v6.4.44"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -6267,7 +6265,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-07-21T09:46:53+00:00"
|
||||
"time": "2026-08-20T09:38:57+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/console",
|
||||
@@ -6369,16 +6367,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/dependency-injection",
|
||||
"version": "v6.4.43",
|
||||
"version": "v6.4.44",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/dependency-injection.git",
|
||||
"reference": "f11815fd2f5f80ce94bf527b5ecfd4bd0b1cc905"
|
||||
"reference": "675b2dfaf70bdca37d13816582023e3bac5fc2d1"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/dependency-injection/zipball/f11815fd2f5f80ce94bf527b5ecfd4bd0b1cc905",
|
||||
"reference": "f11815fd2f5f80ce94bf527b5ecfd4bd0b1cc905",
|
||||
"url": "https://api.github.com/repos/symfony/dependency-injection/zipball/675b2dfaf70bdca37d13816582023e3bac5fc2d1",
|
||||
"reference": "675b2dfaf70bdca37d13816582023e3bac5fc2d1",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -6430,7 +6428,7 @@
|
||||
"description": "Allows you to standardize and centralize the way objects are constructed in your application",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/dependency-injection/tree/v6.4.43"
|
||||
"source": "https://github.com/symfony/dependency-injection/tree/v6.4.44"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -6450,7 +6448,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-07-20T15:13:12+00:00"
|
||||
"time": "2026-08-21T15:20:39+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/deprecation-contracts",
|
||||
@@ -6715,16 +6713,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/error-handler",
|
||||
"version": "v6.4.43",
|
||||
"version": "v6.4.44",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/error-handler.git",
|
||||
"reference": "ecec4c500623ee0ac0c925c3426bcb3e7f429b14"
|
||||
"reference": "8fa59eb915a14b2881993565a122c9dd1b0f8a19"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/error-handler/zipball/ecec4c500623ee0ac0c925c3426bcb3e7f429b14",
|
||||
"reference": "ecec4c500623ee0ac0c925c3426bcb3e7f429b14",
|
||||
"url": "https://api.github.com/repos/symfony/error-handler/zipball/8fa59eb915a14b2881993565a122c9dd1b0f8a19",
|
||||
"reference": "8fa59eb915a14b2881993565a122c9dd1b0f8a19",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -6770,7 +6768,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/v6.4.43"
|
||||
"source": "https://github.com/symfony/error-handler/tree/v6.4.44"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -6790,20 +6788,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-07-21T10:11:50+00:00"
|
||||
"time": "2026-08-20T17:31:15+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/event-dispatcher",
|
||||
"version": "v6.4.43",
|
||||
"version": "v6.4.44",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/event-dispatcher.git",
|
||||
"reference": "ac405d324c10ebbbde6a6e58379bf81db10f1dbf"
|
||||
"reference": "d7110a878e7e7cbd8aaebe9f55da8af885b8c0af"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/ac405d324c10ebbbde6a6e58379bf81db10f1dbf",
|
||||
"reference": "ac405d324c10ebbbde6a6e58379bf81db10f1dbf",
|
||||
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/d7110a878e7e7cbd8aaebe9f55da8af885b8c0af",
|
||||
"reference": "d7110a878e7e7cbd8aaebe9f55da8af885b8c0af",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -6854,7 +6852,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/v6.4.43"
|
||||
"source": "https://github.com/symfony/event-dispatcher/tree/v6.4.44"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -6874,7 +6872,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-07-21T14:00:19+00:00"
|
||||
"time": "2026-08-21T14:01:40+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/event-dispatcher-contracts",
|
||||
@@ -7026,16 +7024,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/filesystem",
|
||||
"version": "v6.4.43",
|
||||
"version": "v6.4.45",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/filesystem.git",
|
||||
"reference": "9ff03da12d67649fbd1f34ca95951554624d0a16"
|
||||
"reference": "6dabf41c21957a2060f101bd7bf7e322989866bd"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/filesystem/zipball/9ff03da12d67649fbd1f34ca95951554624d0a16",
|
||||
"reference": "9ff03da12d67649fbd1f34ca95951554624d0a16",
|
||||
"url": "https://api.github.com/repos/symfony/filesystem/zipball/6dabf41c21957a2060f101bd7bf7e322989866bd",
|
||||
"reference": "6dabf41c21957a2060f101bd7bf7e322989866bd",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -7072,7 +7070,7 @@
|
||||
"description": "Provides basic utilities for the filesystem",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/filesystem/tree/v6.4.43"
|
||||
"source": "https://github.com/symfony/filesystem/tree/v6.4.45"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -7092,20 +7090,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-06-27T10:13:35+00:00"
|
||||
"time": "2026-08-23T07:59:31+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/finder",
|
||||
"version": "v6.4.42",
|
||||
"version": "v6.4.46",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/finder.git",
|
||||
"reference": "0b73dac42493acbadbba644207a715b254e9b029"
|
||||
"reference": "04db08efd85558162ef43fba10c299871b60d60b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/finder/zipball/0b73dac42493acbadbba644207a715b254e9b029",
|
||||
"reference": "0b73dac42493acbadbba644207a715b254e9b029",
|
||||
"url": "https://api.github.com/repos/symfony/finder/zipball/04db08efd85558162ef43fba10c299871b60d60b",
|
||||
"reference": "04db08efd85558162ef43fba10c299871b60d60b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -7140,7 +7138,7 @@
|
||||
"description": "Finds files and directories via an intuitive fluent interface",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/finder/tree/v6.4.42"
|
||||
"source": "https://github.com/symfony/finder/tree/v6.4.46"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -7160,7 +7158,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-06-26T15:18:24+00:00"
|
||||
"time": "2026-09-10T14:17:24+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/flex",
|
||||
@@ -7237,16 +7235,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/framework-bundle",
|
||||
"version": "v6.4.43",
|
||||
"version": "v6.4.46",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/framework-bundle.git",
|
||||
"reference": "dcec940410874561f58edb75e2f3574c8a1340d3"
|
||||
"reference": "407e77216969a366fd2bbf6d96a84c6b4a6500a2"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/framework-bundle/zipball/dcec940410874561f58edb75e2f3574c8a1340d3",
|
||||
"reference": "dcec940410874561f58edb75e2f3574c8a1340d3",
|
||||
"url": "https://api.github.com/repos/symfony/framework-bundle/zipball/407e77216969a366fd2bbf6d96a84c6b4a6500a2",
|
||||
"reference": "407e77216969a366fd2bbf6d96a84c6b4a6500a2",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -7280,7 +7278,7 @@
|
||||
"symfony/form": "<5.4",
|
||||
"symfony/http-client": "<6.3",
|
||||
"symfony/lock": "<5.4",
|
||||
"symfony/mailer": "<5.4",
|
||||
"symfony/mailer": "<6.4.44|>=7.0,<7.4.17|>=8.0,<8.1.5",
|
||||
"symfony/messenger": "<6.3",
|
||||
"symfony/mime": "<6.4.37|>=7.0,<7.4.9",
|
||||
"symfony/property-access": "<5.4",
|
||||
@@ -7366,7 +7364,7 @@
|
||||
"description": "Provides a tight integration between Symfony components and the Symfony full-stack framework",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/framework-bundle/tree/v6.4.43"
|
||||
"source": "https://github.com/symfony/framework-bundle/tree/v6.4.46"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -7386,7 +7384,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-07-26T09:00:38+00:00"
|
||||
"time": "2026-09-13T09:15:59+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/http-client",
|
||||
@@ -7570,16 +7568,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/http-foundation",
|
||||
"version": "v6.4.43",
|
||||
"version": "v6.4.46",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/http-foundation.git",
|
||||
"reference": "ea0c801ec34e9017a8c9363e55c8ef5f1717216e"
|
||||
"reference": "08bbdd5cf09248785442c41929aabe42f8ef5cfd"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/ea0c801ec34e9017a8c9363e55c8ef5f1717216e",
|
||||
"reference": "ea0c801ec34e9017a8c9363e55c8ef5f1717216e",
|
||||
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/08bbdd5cf09248785442c41929aabe42f8ef5cfd",
|
||||
"reference": "08bbdd5cf09248785442c41929aabe42f8ef5cfd",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -7627,7 +7625,7 @@
|
||||
"description": "Defines an object-oriented layer for the HTTP specification",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/http-foundation/tree/v6.4.43"
|
||||
"source": "https://github.com/symfony/http-foundation/tree/v6.4.46"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -7647,20 +7645,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-07-29T06:55:26+00:00"
|
||||
"time": "2026-09-14T17:40:49+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/http-kernel",
|
||||
"version": "v6.4.43",
|
||||
"version": "v6.4.46",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/http-kernel.git",
|
||||
"reference": "cc35556a9bc9e9a6a35fb1114609d1e3c9a63738"
|
||||
"reference": "c662aa16f2a6b078800ef5302444043c38932213"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/http-kernel/zipball/cc35556a9bc9e9a6a35fb1114609d1e3c9a63738",
|
||||
"reference": "cc35556a9bc9e9a6a35fb1114609d1e3c9a63738",
|
||||
"url": "https://api.github.com/repos/symfony/http-kernel/zipball/c662aa16f2a6b078800ef5302444043c38932213",
|
||||
"reference": "c662aa16f2a6b078800ef5302444043c38932213",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -7745,7 +7743,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/v6.4.43"
|
||||
"source": "https://github.com/symfony/http-kernel/tree/v6.4.46"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -7765,7 +7763,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-07-29T11:30:30+00:00"
|
||||
"time": "2026-09-15T07:06:31+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/intl",
|
||||
@@ -8359,16 +8357,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-intl-normalizer",
|
||||
"version": "v1.38.0",
|
||||
"version": "v1.42.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-intl-normalizer.git",
|
||||
"reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b"
|
||||
"reference": "aa20edea75bd9c48cfecc8360922e5a6e5c44502"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/2d446c214bdbe5b71bde5011b060a05fece3ae6b",
|
||||
"reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/aa20edea75bd9c48cfecc8360922e5a6e5c44502",
|
||||
"reference": "aa20edea75bd9c48cfecc8360922e5a6e5c44502",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -8420,7 +8418,7 @@
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.38.0"
|
||||
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.42.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -8440,7 +8438,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-05-25T13:48:31+00:00"
|
||||
"time": "2026-08-07T06:33:24+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-mbstring",
|
||||
@@ -8772,16 +8770,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/process",
|
||||
"version": "v6.4.41",
|
||||
"version": "v6.4.46",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/process.git",
|
||||
"reference": "c8fc09bdfe9fde9aaa89b415a4477feaccec16a7"
|
||||
"reference": "b9f3d9ddf67543b92dbfa6d210c407d7a7982781"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/process/zipball/c8fc09bdfe9fde9aaa89b415a4477feaccec16a7",
|
||||
"reference": "c8fc09bdfe9fde9aaa89b415a4477feaccec16a7",
|
||||
"url": "https://api.github.com/repos/symfony/process/zipball/b9f3d9ddf67543b92dbfa6d210c407d7a7982781",
|
||||
"reference": "b9f3d9ddf67543b92dbfa6d210c407d7a7982781",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -8813,7 +8811,7 @@
|
||||
"description": "Executes commands in sub-processes",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/process/tree/v6.4.41"
|
||||
"source": "https://github.com/symfony/process/tree/v6.4.46"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -8833,20 +8831,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-05-23T13:47:21+00:00"
|
||||
"time": "2026-09-02T12:35:55+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/property-access",
|
||||
"version": "v6.4.32",
|
||||
"version": "v6.4.44",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/property-access.git",
|
||||
"reference": "6dfa655ac9e9860c05cabb287f34da86b18c237e"
|
||||
"reference": "57793e8d1e3ede2d4a65604e8f213a3cfa8d1721"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/property-access/zipball/6dfa655ac9e9860c05cabb287f34da86b18c237e",
|
||||
"reference": "6dfa655ac9e9860c05cabb287f34da86b18c237e",
|
||||
"url": "https://api.github.com/repos/symfony/property-access/zipball/57793e8d1e3ede2d4a65604e8f213a3cfa8d1721",
|
||||
"reference": "57793e8d1e3ede2d4a65604e8f213a3cfa8d1721",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -8894,7 +8892,7 @@
|
||||
"reflection"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/property-access/tree/v6.4.32"
|
||||
"source": "https://github.com/symfony/property-access/tree/v6.4.44"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -8914,20 +8912,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-01-05T08:25:17+00:00"
|
||||
"time": "2026-07-30T11:04:25+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/property-info",
|
||||
"version": "v6.4.43",
|
||||
"version": "v6.4.46",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/property-info.git",
|
||||
"reference": "ff32a0d7885f6f8997ee0aca499fd008852ad5bd"
|
||||
"reference": "4789c9a2650f9285da6db0363ef40e5a21b42ce9"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/property-info/zipball/ff32a0d7885f6f8997ee0aca499fd008852ad5bd",
|
||||
"reference": "ff32a0d7885f6f8997ee0aca499fd008852ad5bd",
|
||||
"url": "https://api.github.com/repos/symfony/property-info/zipball/4789c9a2650f9285da6db0363ef40e5a21b42ce9",
|
||||
"reference": "4789c9a2650f9285da6db0363ef40e5a21b42ce9",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -8984,7 +8982,7 @@
|
||||
"validator"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/property-info/tree/v6.4.43"
|
||||
"source": "https://github.com/symfony/property-info/tree/v6.4.46"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -9004,7 +9002,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-07-22T12:35:29+00:00"
|
||||
"time": "2026-09-03T19:31:46+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/psr-http-message-bridge",
|
||||
@@ -9095,16 +9093,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/routing",
|
||||
"version": "v6.4.43",
|
||||
"version": "v6.4.45",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/routing.git",
|
||||
"reference": "f8dedd42e7a511906aa54be6cebffbbe826ff20e"
|
||||
"reference": "6fe2ed5b356beec41d5720dd73db72887c3f9d6d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/routing/zipball/f8dedd42e7a511906aa54be6cebffbbe826ff20e",
|
||||
"reference": "f8dedd42e7a511906aa54be6cebffbbe826ff20e",
|
||||
"url": "https://api.github.com/repos/symfony/routing/zipball/6fe2ed5b356beec41d5720dd73db72887c3f9d6d",
|
||||
"reference": "6fe2ed5b356beec41d5720dd73db72887c3f9d6d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -9158,7 +9156,7 @@
|
||||
"url"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/routing/tree/v6.4.43"
|
||||
"source": "https://github.com/symfony/routing/tree/v6.4.45"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -9178,7 +9176,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-07-21T13:51:56+00:00"
|
||||
"time": "2026-08-16T17:07:07+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/runtime",
|
||||
@@ -9265,16 +9263,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/security-bundle",
|
||||
"version": "v6.4.43",
|
||||
"version": "v6.4.45",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/security-bundle.git",
|
||||
"reference": "c77d9d6076e6bb8ba706b8616519e3f52f6b2660"
|
||||
"reference": "de06cd0f756ca8fe4894c7e95bac71d90bb94e64"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/security-bundle/zipball/c77d9d6076e6bb8ba706b8616519e3f52f6b2660",
|
||||
"reference": "c77d9d6076e6bb8ba706b8616519e3f52f6b2660",
|
||||
"url": "https://api.github.com/repos/symfony/security-bundle/zipball/de06cd0f756ca8fe4894c7e95bac71d90bb94e64",
|
||||
"reference": "de06cd0f756ca8fe4894c7e95bac71d90bb94e64",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -9357,7 +9355,7 @@
|
||||
"description": "Provides a tight integration of the Security component into the Symfony full-stack framework",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/security-bundle/tree/v6.4.43"
|
||||
"source": "https://github.com/symfony/security-bundle/tree/v6.4.45"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -9377,20 +9375,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-07-29T06:43:01+00:00"
|
||||
"time": "2026-08-30T06:17:28+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/security-core",
|
||||
"version": "v6.4.43",
|
||||
"version": "v6.4.45",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/security-core.git",
|
||||
"reference": "9d2741948b0eb1fc08fd5431909dac8316ba49c2"
|
||||
"reference": "8f74c37460002b97c1318007629c8be37681498a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/security-core/zipball/9d2741948b0eb1fc08fd5431909dac8316ba49c2",
|
||||
"reference": "9d2741948b0eb1fc08fd5431909dac8316ba49c2",
|
||||
"url": "https://api.github.com/repos/symfony/security-core/zipball/8f74c37460002b97c1318007629c8be37681498a",
|
||||
"reference": "8f74c37460002b97c1318007629c8be37681498a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -9417,6 +9415,7 @@
|
||||
"symfony/expression-language": "^5.4|^6.0|^7.0",
|
||||
"symfony/http-foundation": "^5.4|^6.0|^7.0",
|
||||
"symfony/ldap": "^5.4|^6.0|^7.0",
|
||||
"symfony/property-access": "^5.4|^6.0|^7.0",
|
||||
"symfony/string": "^5.4|^6.0|^7.0",
|
||||
"symfony/translation": "^5.4.35|~6.3.12|^6.4.3|^7.0.3",
|
||||
"symfony/validator": "^6.4|^7.0"
|
||||
@@ -9447,7 +9446,7 @@
|
||||
"description": "Symfony Security Component - Core Library",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/security-core/tree/v6.4.43"
|
||||
"source": "https://github.com/symfony/security-core/tree/v6.4.45"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -9467,7 +9466,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-07-13T05:00:30+00:00"
|
||||
"time": "2026-08-30T00:30:12+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/security-csrf",
|
||||
@@ -9543,16 +9542,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/security-http",
|
||||
"version": "v6.4.42",
|
||||
"version": "v6.4.46",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/security-http.git",
|
||||
"reference": "c6a3836e6c331503a841f439912939c4e1be480f"
|
||||
"reference": "a8ad51e48f20b515e589b5927ee7cde7e1e37453"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/security-http/zipball/c6a3836e6c331503a841f439912939c4e1be480f",
|
||||
"reference": "c6a3836e6c331503a841f439912939c4e1be480f",
|
||||
"url": "https://api.github.com/repos/symfony/security-http/zipball/a8ad51e48f20b515e589b5927ee7cde7e1e37453",
|
||||
"reference": "a8ad51e48f20b515e589b5927ee7cde7e1e37453",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -9611,7 +9610,7 @@
|
||||
"description": "Symfony Security Component - HTTP Integration",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/security-http/tree/v6.4.42"
|
||||
"source": "https://github.com/symfony/security-http/tree/v6.4.46"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -9631,7 +9630,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-06-16T12:40:39+00:00"
|
||||
"time": "2026-09-15T05:55:29+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/serializer",
|
||||
@@ -9737,16 +9736,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/service-contracts",
|
||||
"version": "v3.7.1",
|
||||
"version": "v3.7.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/service-contracts.git",
|
||||
"reference": "c0a284bab1ed8aa0417e3d69250ab437739563a0"
|
||||
"reference": "15e6a07ec2a2c75ceb1b21dd98105ee8456d2257"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/c0a284bab1ed8aa0417e3d69250ab437739563a0",
|
||||
"reference": "c0a284bab1ed8aa0417e3d69250ab437739563a0",
|
||||
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/15e6a07ec2a2c75ceb1b21dd98105ee8456d2257",
|
||||
"reference": "15e6a07ec2a2c75ceb1b21dd98105ee8456d2257",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -9800,7 +9799,7 @@
|
||||
"standards"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/service-contracts/tree/v3.7.1"
|
||||
"source": "https://github.com/symfony/service-contracts/tree/v3.7.3"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -9820,7 +9819,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-06-16T09:55:08+00:00"
|
||||
"time": "2026-07-27T15:39:01+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/stimulus-bundle",
|
||||
@@ -9963,16 +9962,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/string",
|
||||
"version": "v6.4.43",
|
||||
"version": "v6.4.46",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/string.git",
|
||||
"reference": "2a8d515c3eaa5d33cf76d5fa277cdadd0a4e5b49"
|
||||
"reference": "4513ab2b0ad2d7845a2f920e2f30b61633c9e606"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/string/zipball/2a8d515c3eaa5d33cf76d5fa277cdadd0a4e5b49",
|
||||
"reference": "2a8d515c3eaa5d33cf76d5fa277cdadd0a4e5b49",
|
||||
"url": "https://api.github.com/repos/symfony/string/zipball/4513ab2b0ad2d7845a2f920e2f30b61633c9e606",
|
||||
"reference": "4513ab2b0ad2d7845a2f920e2f30b61633c9e606",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -10028,7 +10027,7 @@
|
||||
"utf8"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/string/tree/v6.4.43"
|
||||
"source": "https://github.com/symfony/string/tree/v6.4.46"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -10048,7 +10047,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-07-28T07:28:15+00:00"
|
||||
"time": "2026-09-11T09:03:57+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/translation",
|
||||
@@ -10696,16 +10695,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/var-dumper",
|
||||
"version": "v6.4.43",
|
||||
"version": "v6.4.45",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/var-dumper.git",
|
||||
"reference": "6638cfe00907b2b980b749fcf2cd4cd6b2b1396e"
|
||||
"reference": "86a9b2f1bd81c9780a809632238ab6ba10292166"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/var-dumper/zipball/6638cfe00907b2b980b749fcf2cd4cd6b2b1396e",
|
||||
"reference": "6638cfe00907b2b980b749fcf2cd4cd6b2b1396e",
|
||||
"url": "https://api.github.com/repos/symfony/var-dumper/zipball/86a9b2f1bd81c9780a809632238ab6ba10292166",
|
||||
"reference": "86a9b2f1bd81c9780a809632238ab6ba10292166",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -10760,7 +10759,7 @@
|
||||
"dump"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/var-dumper/tree/v6.4.43"
|
||||
"source": "https://github.com/symfony/var-dumper/tree/v6.4.45"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -10780,20 +10779,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-07-06T08:03:42+00:00"
|
||||
"time": "2026-08-30T20:10:39+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/var-exporter",
|
||||
"version": "v6.4.42",
|
||||
"version": "v6.4.45",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/var-exporter.git",
|
||||
"reference": "15e670e0218bfe4efbc0556b8a12ad1304f4c7f4"
|
||||
"reference": "a4e458999ee554a667cd19227f0b97a751bd61e6"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/var-exporter/zipball/15e670e0218bfe4efbc0556b8a12ad1304f4c7f4",
|
||||
"reference": "15e670e0218bfe4efbc0556b8a12ad1304f4c7f4",
|
||||
"url": "https://api.github.com/repos/symfony/var-exporter/zipball/a4e458999ee554a667cd19227f0b97a751bd61e6",
|
||||
"reference": "a4e458999ee554a667cd19227f0b97a751bd61e6",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -10841,7 +10840,7 @@
|
||||
"serialize"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/var-exporter/tree/v6.4.42"
|
||||
"source": "https://github.com/symfony/var-exporter/tree/v6.4.45"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -10861,7 +10860,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-06-27T08:12:01+00:00"
|
||||
"time": "2026-08-23T09:39:49+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/web-link",
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
api_platform:
|
||||
title: The v4 API
|
||||
title: The Goteo v4 API
|
||||
version: 4.0.0
|
||||
show_webby: false
|
||||
collection:
|
||||
order: "DESC"
|
||||
openapi:
|
||||
contact:
|
||||
name: Fundación Platoniq
|
||||
url: http://platoniq.net/contact/
|
||||
email: contacto@platoniq.net
|
||||
name: Democratic Tech Fund
|
||||
url: https://democratictech.fund/contact/
|
||||
email: democratictech@freeknowledge.eu
|
||||
termsOfService:
|
||||
license:
|
||||
name: AGPL
|
||||
|
||||
@@ -2,4 +2,6 @@
|
||||
doctrine_encryption:
|
||||
encrypt_key: "%env(DOCTRINE_ENCRYPTION_ENCRYPT_KEY)%"
|
||||
blind_index_key: "%env(DOCTRINE_ENCRYPTION_BLIND_INDEX_KEY)%"
|
||||
# Necessary for key rotation
|
||||
# https://github.com/Kyzegs/doctrine-encryption-bundle#key-rotation
|
||||
key_id: "2026"
|
||||
|
||||
@@ -26,7 +26,9 @@ framework:
|
||||
http_client:
|
||||
default_options:
|
||||
headers:
|
||||
'User-Agent': 'Platoniq Goteo-v4-API'
|
||||
'User-Agent': 'DTF Goteo v4 API'
|
||||
|
||||
trusted_proxies: '%env(APP_TRUSTED_PROXIES)%'
|
||||
|
||||
when@test:
|
||||
framework:
|
||||
|
||||
@@ -2,6 +2,7 @@ league_oauth2_server:
|
||||
client:
|
||||
classname: App\Entity\OAuth2Client
|
||||
authorization_server:
|
||||
access_token_ttl: '%env(OAUTH_ACCESS_TOKEN_TTL)%'
|
||||
private_key: '%env(resolve:OAUTH_PRIVATE_KEY)%'
|
||||
private_key_passphrase: '%env(resolve:OAUTH_PASSPHRASE)%'
|
||||
encryption_key: '%env(resolve:OAUTH_ENCRYPTION_KEY)%'
|
||||
|
||||
@@ -13,7 +13,7 @@ security:
|
||||
app_user_provider:
|
||||
entity:
|
||||
class: App\Entity\User\User
|
||||
property: handle
|
||||
property: id
|
||||
login_user_provider:
|
||||
id: App\Security\LoginUserProvider
|
||||
firewalls:
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
# Put parameters here that don't need to change on each machine where the app is deployed
|
||||
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
|
||||
parameters:
|
||||
env(OAUTH_ACCESS_TOKEN_TTL): PT1H
|
||||
|
||||
services:
|
||||
# default configuration for services in *this* file
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20260929170504 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add to project_reward_claim a status and shipping address.';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE project_reward_claim ADD status VARCHAR(255) NOT NULL, ADD shipping_address_first_name VARCHAR(255) NOT NULL, ADD shipping_address_last_name VARCHAR(255) NOT NULL, ADD shipping_address_address_line1 VARCHAR(255) NOT NULL, ADD shipping_address_address_line2 VARCHAR(255) DEFAULT NULL, ADD shipping_address_city VARCHAR(255) NOT NULL, ADD shipping_address_post_code VARCHAR(255) NOT NULL, ADD shipping_address_country VARCHAR(2) NOT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE project_reward_claim DROP status, DROP shipping_address_first_name, DROP shipping_address_last_name, DROP shipping_address_address_line1, DROP shipping_address_address_line2, DROP shipping_address_city, DROP shipping_address_post_code, DROP shipping_address_country');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20261001120621 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add address table';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('CREATE TABLE address (id INT AUTO_INCREMENT NOT NULL, user_id INT NOT NULL, first_name VARCHAR(255) NOT NULL, last_name VARCHAR(255) NOT NULL, line1 VARCHAR(255) NOT NULL, line2 VARCHAR(255) DEFAULT NULL, city VARCHAR(255) NOT NULL, post_code VARCHAR(255) NOT NULL, country VARCHAR(2) NOT NULL, INDEX IDX_D4E6F81A76ED395 (user_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('ALTER TABLE address ADD CONSTRAINT FK_D4E6F81A76ED395 FOREIGN KEY (user_id) REFERENCES user (id)');
|
||||
$this->addSql('ALTER TABLE project_reward_claim ADD address_id INT DEFAULT NULL, DROP shipping_address_first_name, DROP shipping_address_last_name, DROP shipping_address_address_line1, DROP shipping_address_address_line2, DROP shipping_address_city, DROP shipping_address_post_code, DROP shipping_address_country');
|
||||
$this->addSql('ALTER TABLE project_reward_claim ADD CONSTRAINT FK_EA126118F5B7AF75 FOREIGN KEY (address_id) REFERENCES address (id)');
|
||||
$this->addSql('CREATE INDEX IDX_EA126118F5B7AF75 ON project_reward_claim (address_id)');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE project_reward_claim DROP FOREIGN KEY FK_EA126118F5B7AF75');
|
||||
$this->addSql('ALTER TABLE address DROP FOREIGN KEY FK_D4E6F81A76ED395');
|
||||
$this->addSql('DROP TABLE address');
|
||||
$this->addSql('DROP INDEX IDX_EA126118F5B7AF75 ON project_reward_claim');
|
||||
$this->addSql('ALTER TABLE project_reward_claim ADD shipping_address_first_name VARCHAR(255) DEFAULT NULL, ADD shipping_address_last_name VARCHAR(255) DEFAULT NULL, ADD shipping_address_address_line1 VARCHAR(255) DEFAULT NULL, ADD shipping_address_address_line2 VARCHAR(255) DEFAULT NULL, ADD shipping_address_city VARCHAR(255) DEFAULT NULL, ADD shipping_address_post_code VARCHAR(255) DEFAULT NULL, ADD shipping_address_country VARCHAR(2) DEFAULT NULL, DROP address_id');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20261006114714 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add review, review_area and review_comment tables';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('CREATE TABLE review (id INT AUTO_INCREMENT NOT NULL, project_id INT NOT NULL, reviewer_id INT DEFAULT NULL, type VARCHAR(255) NOT NULL, date_created DATETIME NOT NULL, date_updated DATETIME NOT NULL, INDEX IDX_794381C6166D1F9C (project_id), INDEX IDX_794381C670574616 (reviewer_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('CREATE TABLE review_area (id INT AUTO_INCREMENT NOT NULL, review_id INT NOT NULL, title VARCHAR(255) NOT NULL, summary LONGTEXT DEFAULT NULL, risk VARCHAR(255) NOT NULL, date_created DATETIME NOT NULL, date_updated DATETIME NOT NULL, INDEX IDX_352070703E2E969B (review_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('CREATE TABLE review_comment (id INT AUTO_INCREMENT NOT NULL, author_id INT DEFAULT NULL, area_id INT NOT NULL, body LONGTEXT NOT NULL, date_created DATETIME NOT NULL, date_updated DATETIME NOT NULL, INDEX IDX_F9AE69BF675F31B (author_id), INDEX IDX_F9AE69BBD0F409C (area_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('ALTER TABLE review ADD CONSTRAINT FK_794381C6166D1F9C FOREIGN KEY (project_id) REFERENCES project (id)');
|
||||
$this->addSql('ALTER TABLE review ADD CONSTRAINT FK_794381C670574616 FOREIGN KEY (reviewer_id) REFERENCES user (id)');
|
||||
$this->addSql('ALTER TABLE review_area ADD CONSTRAINT FK_352070703E2E969B FOREIGN KEY (review_id) REFERENCES review (id)');
|
||||
$this->addSql('ALTER TABLE review_comment ADD CONSTRAINT FK_F9AE69BF675F31B FOREIGN KEY (author_id) REFERENCES user (id)');
|
||||
$this->addSql('ALTER TABLE review_comment ADD CONSTRAINT FK_F9AE69BBD0F409C FOREIGN KEY (area_id) REFERENCES review_area (id)');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE review DROP FOREIGN KEY FK_794381C6166D1F9C');
|
||||
$this->addSql('ALTER TABLE review DROP FOREIGN KEY FK_794381C670574616');
|
||||
$this->addSql('ALTER TABLE review_area DROP FOREIGN KEY FK_352070703E2E969B');
|
||||
$this->addSql('ALTER TABLE review_comment DROP FOREIGN KEY FK_F9AE69BF675F31B');
|
||||
$this->addSql('ALTER TABLE review_comment DROP FOREIGN KEY FK_F9AE69BBD0F409C');
|
||||
$this->addSql('DROP TABLE review');
|
||||
$this->addSql('DROP TABLE review_area');
|
||||
$this->addSql('DROP TABLE review_comment');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20261007154315 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add theme tables';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('CREATE TABLE project_theme (project_id INT NOT NULL, theme_id INT NOT NULL, INDEX IDX_8420EE96166D1F9C (project_id), INDEX IDX_8420EE9659027487 (theme_id), PRIMARY KEY(project_id, theme_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('CREATE TABLE theme (id INT AUTO_INCREMENT NOT NULL, slug VARCHAR(56) NOT NULL, name VARCHAR(255) NOT NULL, locales JSON NOT NULL COMMENT \'(DC2Type:json)\', UNIQUE INDEX UNIQ_9775E708989D9B62 (slug), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('ALTER TABLE project_theme ADD CONSTRAINT FK_8420EE96166D1F9C FOREIGN KEY (project_id) REFERENCES project (id) ON DELETE CASCADE');
|
||||
$this->addSql('ALTER TABLE project_theme ADD CONSTRAINT FK_8420EE9659027487 FOREIGN KEY (theme_id) REFERENCES theme (id) ON DELETE CASCADE');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE project_theme DROP FOREIGN KEY FK_8420EE96166D1F9C');
|
||||
$this->addSql('ALTER TABLE project_theme DROP FOREIGN KEY FK_8420EE9659027487');
|
||||
$this->addSql('DROP TABLE project_theme');
|
||||
$this->addSql('DROP TABLE theme');
|
||||
}
|
||||
}
|
||||
Generated
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "org.goteo.api",
|
||||
"name": "dtf-api",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
|
||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace App\ApiResource;
|
||||
|
||||
use ApiPlatform\Doctrine\Orm\State\Options;
|
||||
use ApiPlatform\Metadata as API;
|
||||
use App\ApiResource\User\UserApiResource;
|
||||
use App\Entity\Address;
|
||||
use App\State\ApiResourceStateProcessor;
|
||||
use App\State\ApiResourceStateProvider;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
* Addresses represent named locations that postal services can reach.
|
||||
*/
|
||||
#[API\ApiResource(
|
||||
shortName: 'Address',
|
||||
stateOptions: new Options(entityClass: Address::class),
|
||||
processor: ApiResourceStateProcessor::class,
|
||||
provider: ApiResourceStateProvider::class,
|
||||
)]
|
||||
class AddressApiResource
|
||||
{
|
||||
#[API\ApiProperty(identifier: true, writable: false)]
|
||||
public int $id;
|
||||
|
||||
#[Assert\NotBlank()]
|
||||
#[API\ApiProperty(security: 'is_granted("USER_EDIT", object.user)')]
|
||||
public UserApiResource $user;
|
||||
|
||||
/**
|
||||
* First name(s) of the person receiving the shipment.
|
||||
*/
|
||||
#[Assert\NotBlank()]
|
||||
public string $firstName;
|
||||
|
||||
/**
|
||||
* Last name(s) of the person receiving the shipment.
|
||||
*/
|
||||
#[Assert\NotBlank()]
|
||||
public string $lastName;
|
||||
|
||||
/**
|
||||
* Line 1: usually street name and number.
|
||||
*/
|
||||
#[Assert\NotBlank()]
|
||||
public string $line1;
|
||||
|
||||
/**
|
||||
* Line 2: additional data like apartment number, door, etc.
|
||||
*/
|
||||
public ?string $line2 = null;
|
||||
|
||||
/**
|
||||
* Name of the city, or the lowest-available type of settlement to which the address lines belong.
|
||||
*/
|
||||
#[Assert\NotBlank()]
|
||||
public string $city;
|
||||
|
||||
/**
|
||||
* Postal/PIN/ZIP code to which the address lines belong.
|
||||
*/
|
||||
#[Assert\NotBlank()]
|
||||
public string $postCode;
|
||||
|
||||
/**
|
||||
* ISO 3166-1 alpha-2 two-letter country code.\
|
||||
* e.g: ES (Spain).
|
||||
*/
|
||||
#[Assert\NotBlank()]
|
||||
#[Assert\Country()]
|
||||
public string $country;
|
||||
}
|
||||
@@ -12,9 +12,8 @@ use App\State\CategoryStateProvider;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
* A Category can be used by other resources as a "topic intent".\
|
||||
* For example. Projects might relate with up to 2 Categories, which are used by the Project
|
||||
* as a way to describe itself and can be used to discover similar Projects.\
|
||||
* A Category, like in books, is a way of grouping resources by type, genre, audience, or subject, for example:
|
||||
* science-fiction, biography, fantasy, etc.\
|
||||
* \
|
||||
* Categories can only be modified by users with the role "ROLE_ADMIN", but can usually
|
||||
* be referenced by non-admin users in their own resources, such as Project owners.
|
||||
|
||||
@@ -10,6 +10,7 @@ use App\ApiResource\Accounting\AccountingApiResource;
|
||||
use App\ApiResource\CategoryApiResource;
|
||||
use App\ApiResource\LocalizedApiResourceTrait;
|
||||
use App\ApiResource\Matchfunding\MatchCallSubmissionApiResource;
|
||||
use App\ApiResource\ThemeApiResource;
|
||||
use App\ApiResource\TimestampedCreationApiResource;
|
||||
use App\ApiResource\TimestampedUpdationApiResource;
|
||||
use App\ApiResource\User\UserApiResource;
|
||||
@@ -134,6 +135,16 @@ class ProjectApiResource
|
||||
#[API\ApiFilter(filterClass: SearchFilter::class, strategy: 'exact')]
|
||||
public array $categories;
|
||||
|
||||
/**
|
||||
* A list of the available Themes of this Project.
|
||||
*
|
||||
* @var ThemeApiResource[]
|
||||
*/
|
||||
#[Assert\NotBlank()]
|
||||
#[API\ApiProperty(writableLink: false)]
|
||||
#[API\ApiFilter(filterClass: SearchFilter::class, strategy: 'exact')]
|
||||
public array $themes;
|
||||
|
||||
/**
|
||||
* ISO 3166 data about the Project's territory of interest.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace App\ApiResource\Project;
|
||||
|
||||
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
|
||||
use ApiPlatform\Doctrine\Orm\State\Options;
|
||||
use ApiPlatform\Metadata as API;
|
||||
use App\ApiResource\TimestampedCreationApiResource;
|
||||
use App\ApiResource\TimestampedUpdationApiResource;
|
||||
use App\ApiResource\User\UserApiResource;
|
||||
use App\Entity\Project\Review;
|
||||
use App\Entity\Project\ReviewType;
|
||||
use App\State\ApiResourceStateProcessor;
|
||||
use App\State\ApiResourceStateProvider;
|
||||
|
||||
/**
|
||||
* ProjectReviews are launched as health-checks for Projects.\
|
||||
* \
|
||||
* The reviews are focused on certain aspects of a Project's lifecycle. The types `campaign` and `financial` reviews
|
||||
* are to evaluate the fitness of a Project before being greenlit for campaigning or the legitimacy of their raised funds, respectively.\
|
||||
* \
|
||||
* ProjectReview resources cannot be manually created, they are created automatically when a Project moves into a "to review" status:
|
||||
* - `to_campaign_review`: will launch a related ProjectReview of `campaign` type
|
||||
* - `to_financial_review`: will launch a related ProjectReview of `financial` type.
|
||||
*/
|
||||
#[API\ApiResource(
|
||||
shortName: 'ProjectReview',
|
||||
stateOptions: new Options(entityClass: Review::class),
|
||||
provider: ApiResourceStateProvider::class,
|
||||
processor: ApiResourceStateProcessor::class
|
||||
)]
|
||||
#[API\GetCollection()]
|
||||
#[API\Get()]
|
||||
#[API\Delete()]
|
||||
#[API\Patch()]
|
||||
class ReviewApiResource
|
||||
{
|
||||
use TimestampedCreationApiResource;
|
||||
use TimestampedUpdationApiResource;
|
||||
|
||||
#[API\ApiProperty(identifier: true, writable: false)]
|
||||
public int $id;
|
||||
|
||||
#[API\ApiFilter(SearchFilter::class, strategy: 'exact')]
|
||||
public ProjectApiResource $project;
|
||||
|
||||
#[API\ApiFilter(SearchFilter::class, strategy: 'exact')]
|
||||
public UserApiResource $reviewer;
|
||||
|
||||
#[API\ApiFilter(SearchFilter::class, strategy: 'exact')]
|
||||
public ReviewType $type;
|
||||
|
||||
/**
|
||||
* @var ReviewAreaApiResource[]
|
||||
*/
|
||||
public array $areas;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace App\ApiResource\Project;
|
||||
|
||||
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
|
||||
use ApiPlatform\Doctrine\Orm\State\Options;
|
||||
use ApiPlatform\Metadata as API;
|
||||
use App\ApiResource\TimestampedCreationApiResource;
|
||||
use App\ApiResource\TimestampedUpdationApiResource;
|
||||
use App\Entity\Project\ReviewArea;
|
||||
use App\Entity\Project\ReviewAreaRisk;
|
||||
use App\State\ApiResourceStateProcessor;
|
||||
use App\State\ApiResourceStateProvider;
|
||||
|
||||
/**
|
||||
* A ProjectReviewArea represents one specific topic of evaluation for ProjectReviews.\
|
||||
* \
|
||||
* Conversations, feedback and evolution of the ProjectReview must happen around specific areas of review.\
|
||||
* Each area holds an associated risk, which represents the trust the reviewer has for the reviewed Project's health in that area,
|
||||
* areas can only be updated by the assigned reviewer or an user with `ROLE_ADMIN`.
|
||||
*/
|
||||
#[API\ApiResource(
|
||||
shortName: 'ProjectReviewArea',
|
||||
stateOptions: new Options(entityClass: ReviewArea::class),
|
||||
provider: ApiResourceStateProvider::class,
|
||||
processor: ApiResourceStateProcessor::class,
|
||||
)]
|
||||
#[API\GetCollection()]
|
||||
#[API\Post(security: 'is_granted("ROLE_ADMIN")')]
|
||||
#[API\Get(security: 'is_granted("REVIEWAREA_VIEW", object)')]
|
||||
#[API\Patch(security: 'is_granted("REVIEWAREA_EDIT", object)')]
|
||||
#[API\Delete(security: 'is_granted("REVIEWAREA_EDIT", object)')]
|
||||
class ReviewAreaApiResource
|
||||
{
|
||||
use TimestampedCreationApiResource;
|
||||
use TimestampedUpdationApiResource;
|
||||
|
||||
#[API\ApiProperty(identifier: true, writable: false)]
|
||||
public int $id;
|
||||
|
||||
#[API\ApiFilter(SearchFilter::class, strategy: 'exact')]
|
||||
public ReviewApiResource $review;
|
||||
|
||||
public string $title;
|
||||
|
||||
public string $summary;
|
||||
|
||||
public ReviewAreaRisk $risk;
|
||||
|
||||
/**
|
||||
* @var ReviewCommentApiResource[]
|
||||
*/
|
||||
public array $comments;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\ApiResource\Project;
|
||||
|
||||
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
|
||||
use ApiPlatform\Doctrine\Orm\State\Options;
|
||||
use ApiPlatform\Metadata as API;
|
||||
use App\ApiResource\TimestampedCreationApiResource;
|
||||
use App\ApiResource\TimestampedUpdationApiResource;
|
||||
use App\ApiResource\User\UserApiResource;
|
||||
use App\Entity\Project\ReviewComment;
|
||||
use App\State\ApiResourceStateProcessor;
|
||||
use App\State\ApiResourceStateProvider;
|
||||
use App\Validator\UserOwned;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
* ProjectReviewComments hold the conversation between the reviewer and the reviewed Project owner.
|
||||
*/
|
||||
#[API\ApiResource(
|
||||
shortName: 'ProjectReviewComment',
|
||||
stateOptions: new Options(entityClass: ReviewComment::class),
|
||||
provider: ApiResourceStateProvider::class,
|
||||
processor: ApiResourceStateProcessor::class,
|
||||
)]
|
||||
#[API\GetCollection()]
|
||||
#[API\Post(securityPostDenormalize: 'is_granted("REVIEWAREA_VIEW", object.area)')]
|
||||
#[API\Get(security: 'is_granted("REVIEWAREA_VIEW", object.area)')]
|
||||
#[API\Delete(security: 'is_granted("REVIEWAREA_VIEW", object.area)')]
|
||||
class ReviewCommentApiResource
|
||||
{
|
||||
use TimestampedCreationApiResource;
|
||||
use TimestampedUpdationApiResource;
|
||||
|
||||
#[API\ApiProperty(identifier: true, writable: false)]
|
||||
public int $id;
|
||||
|
||||
#[Assert\NotBlank()]
|
||||
#[API\ApiFilter(SearchFilter::class, strategy: 'exact')]
|
||||
public ReviewAreaApiResource $area;
|
||||
|
||||
#[UserOwned()]
|
||||
#[Assert\NotBlank()]
|
||||
#[API\ApiFilter(SearchFilter::class, strategy: 'exact')]
|
||||
public UserApiResource $author;
|
||||
|
||||
#[Assert\NotBlank()]
|
||||
#[Assert\Length(min: 15)]
|
||||
public string $body;
|
||||
}
|
||||
@@ -5,17 +5,21 @@ namespace App\ApiResource\Project;
|
||||
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
|
||||
use ApiPlatform\Doctrine\Orm\State\Options;
|
||||
use ApiPlatform\Metadata as API;
|
||||
use App\ApiResource\AddressApiResource;
|
||||
use App\ApiResource\Gateway\ChargeApiResource;
|
||||
use App\ApiResource\User\UserApiResource;
|
||||
use App\Dto\RewardClaimCreationDto;
|
||||
use App\Dto\RewardClaimUpdationDto;
|
||||
use App\Entity\Project\RewardClaim;
|
||||
use App\Entity\Project\RewardClaimStatus;
|
||||
use App\State\ApiResourceStateProvider;
|
||||
use App\State\Project\RewardClaimStateProcessor;
|
||||
use App\Validator\AvailableRewardUnits;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
* A ProjectRewardClaim represents the will of an User who wishes to obtain one ProjectReward.
|
||||
* A ProjectRewardClaim represents the will of an User who wishes to obtain one ProjectReward.\
|
||||
* \
|
||||
* Once created ProjectRewardClaims can only be deleted by the User who owns it,
|
||||
* while their status can only be updated by the User who owns the Project of the claimed ProjectReward.
|
||||
*/
|
||||
#[API\ApiResource(
|
||||
shortName: 'ProjectRewardClaim',
|
||||
@@ -26,31 +30,43 @@ use Symfony\Component\Validator\Constraints as Assert;
|
||||
#[API\GetCollection()]
|
||||
#[API\Post(input: RewardClaimCreationDto::class)]
|
||||
#[API\Get()]
|
||||
#[API\Delete()]
|
||||
#[API\Patch(input: RewardClaimUpdationDto::class)]
|
||||
#[API\Delete(security: 'is_granted("CLAIM_OWNS", object)')]
|
||||
class RewardClaimApiResource
|
||||
{
|
||||
#[API\ApiProperty(identifier: true, writable: false)]
|
||||
public int $id;
|
||||
|
||||
/**
|
||||
* The User claiming the ProjectReward.
|
||||
* The User claiming the ProjectReward. Derived from the GatewayCharge.
|
||||
*/
|
||||
#[API\ApiProperty(writable: false)]
|
||||
#[API\ApiFilter(SearchFilter::class, strategy: 'exact')]
|
||||
public UserApiResource $owner;
|
||||
|
||||
/**
|
||||
* The GatewayCharge granting access to the ProjectReward.
|
||||
*/
|
||||
#[Assert\NotBlank()]
|
||||
#[API\ApiFilter(SearchFilter::class, strategy: 'exact')]
|
||||
public ChargeApiResource $charge;
|
||||
|
||||
/**
|
||||
* The ProjectReward being claimed.
|
||||
*/
|
||||
#[Assert\NotBlank()]
|
||||
#[AvailableRewardUnits()]
|
||||
#[API\ApiFilter(SearchFilter::class, strategy: 'exact')]
|
||||
public RewardApiResource $reward;
|
||||
|
||||
/**
|
||||
* The point at which the claim is in its life-cylce.
|
||||
*/
|
||||
#[API\ApiFilter(SearchFilter::class, strategy: 'exact')]
|
||||
public RewardClaimStatus $status;
|
||||
|
||||
/**
|
||||
* Only used when the reward is a physical object that needs to be shipped.
|
||||
*/
|
||||
#[API\ApiProperty(readableLink: true)]
|
||||
#[API\ApiFilter(SearchFilter::class, properties: ['address.city' => 'partial'])]
|
||||
#[API\ApiFilter(SearchFilter::class, properties: ['address.postCode' => 'partial'])]
|
||||
#[API\ApiFilter(SearchFilter::class, properties: ['address.country' => 'partial'])]
|
||||
public ?AddressApiResource $address;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace App\ApiResource;
|
||||
|
||||
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
|
||||
use ApiPlatform\Doctrine\Orm\State\Options;
|
||||
use ApiPlatform\Metadata as API;
|
||||
use App\Entity\Theme;
|
||||
use App\State\ApiResourceStateProcessor;
|
||||
use App\State\ApiResourceStateProvider;
|
||||
use App\State\ThemeStateProvider;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
* A Theme, like in books, works as a "topic intent" grouping, for example:
|
||||
* friendship, good vs. evil, power and corruption, etc.\
|
||||
* \
|
||||
* Themes can only be modified by users with the role "ROLE_ADMIN", but can usually
|
||||
* be referenced by non-admin users in their own resources, such as Project owners.
|
||||
*/
|
||||
#[API\ApiResource(
|
||||
shortName: 'Theme',
|
||||
stateOptions: new Options(entityClass: Theme::class),
|
||||
provider: ApiResourceStateProvider::class,
|
||||
processor: ApiResourceStateProcessor::class,
|
||||
)]
|
||||
#[API\GetCollection()]
|
||||
#[API\Post(security: 'is_granted("ROLE_ADMIN")')]
|
||||
#[API\Get(
|
||||
provider: ThemeStateProvider::class,
|
||||
uriTemplate: '/themes/{idOrSlug}',
|
||||
uriVariables: [
|
||||
'idOrSlug' => new API\Link(
|
||||
description: 'Theme identifier or slug',
|
||||
),
|
||||
]
|
||||
)]
|
||||
#[API\Patch(security: 'is_granted("ROLE_ADMIN")')]
|
||||
#[API\Delete(security: 'is_granted("ROLE_ADMIN")')]
|
||||
class ThemeApiResource
|
||||
{
|
||||
use LocalizedApiResourceTrait;
|
||||
|
||||
/**
|
||||
* This value will identify this Theme in relationships with other resources.
|
||||
*/
|
||||
#[API\ApiProperty(identifier: true)]
|
||||
public string $id;
|
||||
|
||||
/**
|
||||
* A unique, non white space, string identifier for this Theme.
|
||||
*/
|
||||
#[API\ApiProperty(writable: false)]
|
||||
#[API\ApiFilter(SearchFilter::class, strategy: 'exact')]
|
||||
public string $slug;
|
||||
|
||||
/**
|
||||
* A human-readable self-descriptive string of what this Theme is about.
|
||||
*/
|
||||
#[Assert\NotBlank()]
|
||||
#[API\ApiProperty()]
|
||||
#[API\ApiFilter(SearchFilter::class, strategy: 'partial')]
|
||||
public string $name;
|
||||
}
|
||||
@@ -9,6 +9,7 @@ use App\ApiResource\Accounting\AccountingApiResource;
|
||||
use App\Entity\Tipjar;
|
||||
use App\State\ApiResourceStateProcessor;
|
||||
use App\State\ApiResourceStateProvider;
|
||||
use App\Validator\NotExisting;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
@@ -40,6 +41,7 @@ class TipjarApiResource
|
||||
* Human readable, non white space, unique string.
|
||||
*/
|
||||
#[Assert\NotBlank()]
|
||||
#[NotExisting(Tipjar::class, 'name')]
|
||||
#[API\ApiFilter(SearchFilter::class, strategy: 'partial')]
|
||||
public string $name;
|
||||
}
|
||||
|
||||
@@ -42,12 +42,13 @@ class OrganizationApiResource
|
||||
public UserApiResource $user;
|
||||
|
||||
/**
|
||||
* ID for tax purposes. e.g: NIF (formerly CIF), Umsatzsteuer-Id, EID, etc.
|
||||
* Organization ID for tax purposes. e.g: NIF (formerly CIF), Umsatzsteuer-Id, EID, etc.
|
||||
*/
|
||||
#[Assert\NotBlank()]
|
||||
#[Assert\Length(min: 2)]
|
||||
#[API\ApiProperty(
|
||||
securityPostDenormalize: 'is_granted("ORGANIZATION_EDIT", previous_object)',
|
||||
)]
|
||||
#[Assert\NotBlank()]
|
||||
public string $taxId;
|
||||
|
||||
/**
|
||||
@@ -55,10 +56,11 @@ class OrganizationApiResource
|
||||
* as it appears on legal documents issued by or for this organization.\
|
||||
* Will be used as last option for the display name of the User.
|
||||
*/
|
||||
#[Assert\NotBlank()]
|
||||
#[Assert\Length(min: 1)]
|
||||
#[API\ApiProperty(
|
||||
securityPostDenormalize: 'is_granted("ORGANIZATION_EDIT", previous_object)',
|
||||
)]
|
||||
#[Assert\NotBlank()]
|
||||
public string $legalName;
|
||||
|
||||
/**
|
||||
@@ -66,6 +68,7 @@ class OrganizationApiResource
|
||||
* Might be similar to the legal name or completely different.\
|
||||
* Will be used as display name for the User when present.
|
||||
*/
|
||||
#[Assert\Length(min: 1)]
|
||||
#[API\ApiProperty(
|
||||
securityPostDenormalize: 'is_granted("ORGANIZATION_EDIT", previous_object)',
|
||||
)]
|
||||
|
||||
@@ -7,6 +7,7 @@ use ApiPlatform\Metadata as API;
|
||||
use App\Entity\User\Person;
|
||||
use App\State\ApiResourceStateProcessor;
|
||||
use App\State\ApiResourceStateProvider;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
* Most times a real person is behind a User account,
|
||||
@@ -43,6 +44,7 @@ class PersonApiResource
|
||||
/**
|
||||
* Personal ID for tax purposes. e.g: NIF, Steuer-ID, SSN, ITIN, etc.
|
||||
*/
|
||||
#[Assert\Length(min: 2)]
|
||||
#[API\ApiProperty(
|
||||
securityPostDenormalize: 'is_granted("PERSON_EDIT", previous_object)',
|
||||
)]
|
||||
@@ -52,6 +54,7 @@ class PersonApiResource
|
||||
* First-part of the name of the person,
|
||||
* in most western conventions this is the given name(s). e.g: John, Juan, etc.
|
||||
*/
|
||||
#[Assert\Length(min: 1)]
|
||||
#[API\ApiProperty(
|
||||
securityPostDenormalize: 'is_granted("PERSON_EDIT", previous_object)',
|
||||
)]
|
||||
@@ -59,8 +62,9 @@ class PersonApiResource
|
||||
|
||||
/**
|
||||
* Last-part of the name of the person,
|
||||
* in most western conventions this is the family name(s). e.g: Smith, Herrera García, etc.
|
||||
* in most western conventions this is the family name(s). e.g: Smith, Herrera, etc.
|
||||
*/
|
||||
#[Assert\Length(min: 1)]
|
||||
#[API\ApiProperty(
|
||||
securityPostDenormalize: 'is_granted("PERSON_EDIT", previous_object)',
|
||||
)]
|
||||
|
||||
@@ -10,7 +10,8 @@ use ApiPlatform\Metadata as API;
|
||||
use App\ApiResource\Accounting\AccountingApiResource;
|
||||
use App\ApiResource\TimestampedCreationApiResource;
|
||||
use App\ApiResource\TimestampedUpdationApiResource;
|
||||
use App\Dto\UserSignupDto;
|
||||
use App\Dto\User\UserSignupDto;
|
||||
use App\Dto\User\UserUpdationDto;
|
||||
use App\Entity\Territory;
|
||||
use App\Entity\User\User;
|
||||
use App\Entity\User\UserType;
|
||||
@@ -48,8 +49,11 @@ use Symfony\Component\Validator\Constraints as Assert;
|
||||
),
|
||||
]
|
||||
)]
|
||||
#[API\Patch(securityPostDenormalize: 'is_granted("USER_EDIT", previous_object)')]
|
||||
#[API\Delete(securityPostDenormalize: 'is_granted("USER_EDIT", previous_object)')]
|
||||
#[API\Patch(
|
||||
input: UserUpdationDto::class,
|
||||
securityPostDenormalize: 'is_granted("USER_EDIT", previous_object)'
|
||||
)]
|
||||
#[API\Delete(securityPostDenormalize: 'is_granted("ROLE_ADMIN", previous_object)')]
|
||||
#[API\ApiFilter(QFilter::class, properties: [
|
||||
'q' => [
|
||||
'email',
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
|
||||
namespace App\Benzina;
|
||||
|
||||
use App\Entity\Theme;
|
||||
use App\Repository\ThemeRepository;
|
||||
use Symfony\Contracts\Service\Attribute\Required;
|
||||
|
||||
trait CategorizationPumpTrait
|
||||
{
|
||||
private ThemeRepository $themeRepository;
|
||||
|
||||
private const THEMES = [
|
||||
'education' => 'Educación',
|
||||
'culture' => 'Cultura',
|
||||
'journalism' => 'Periodismo',
|
||||
'science-and-tech' => 'Ciencia y Tecnología',
|
||||
'rural-and-ecology' => 'Desarrollo rural y Transición ecológica',
|
||||
'health-and-cares' => 'Salud y Cuidados',
|
||||
'sustainability' => 'Sostenibilidad',
|
||||
'democracy' => 'Derechos y Democracia',
|
||||
'social-economy' => 'Economía social',
|
||||
];
|
||||
|
||||
#[Required]
|
||||
public function setThemeRepository(ThemeRepository $themeRepository)
|
||||
{
|
||||
$this->themeRepository = $themeRepository;
|
||||
}
|
||||
|
||||
public function getTheme(string $slug): Theme
|
||||
{
|
||||
$theme = $this->themeRepository->findOneBy(['slug' => $slug]);
|
||||
|
||||
if (!$theme) {
|
||||
$theme = new Theme();
|
||||
$theme->setSlug($slug);
|
||||
$theme->setName(self::THEMES[$slug]);
|
||||
}
|
||||
|
||||
$theme->addLocale('es');
|
||||
|
||||
return $theme;
|
||||
}
|
||||
|
||||
private function matchCategories(array $categories, array $names): bool
|
||||
{
|
||||
$categories = array_map(fn($c) => $c['name'], $categories);
|
||||
$matches = 0;
|
||||
|
||||
foreach ($names as $name) {
|
||||
if (in_array($name, $categories)) {
|
||||
$matches = $matches + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return $matches === count($names);
|
||||
}
|
||||
|
||||
private function mapOldCategoriesToThemes(string $socialCommitmentName, array $categories): array
|
||||
{
|
||||
$themes = [];
|
||||
|
||||
if (
|
||||
$socialCommitmentName === 'Solidario'
|
||||
&& $this->matchCategories($categories, ['Social'])
|
||||
) {
|
||||
$themes[] = $this->getTheme('democracy');
|
||||
}
|
||||
|
||||
if (
|
||||
$socialCommitmentName === 'Software libre'
|
||||
&& $this->matchCategories($categories, ['Tecnológico', 'Científico'])
|
||||
) {
|
||||
$themes[] = $this->getTheme('science-and-tech');
|
||||
}
|
||||
|
||||
if (
|
||||
$socialCommitmentName === 'Generar empleo'
|
||||
&& $this->matchCategories($categories, ['Emprendedor', 'Social'])
|
||||
) {
|
||||
$themes[] = $this->getTheme('social-economy');
|
||||
}
|
||||
|
||||
if (
|
||||
$socialCommitmentName === 'Periodismo independiente'
|
||||
&& $this->matchCategories($categories, ['Comunicativo'])
|
||||
) {
|
||||
$themes[] = $this->getTheme('journalism');
|
||||
}
|
||||
|
||||
if (
|
||||
$socialCommitmentName === 'Educativo'
|
||||
&& $this->matchCategories($categories, ['Educativo'])
|
||||
) {
|
||||
$themes[] = $this->getTheme('education');
|
||||
}
|
||||
|
||||
if (
|
||||
$socialCommitmentName === 'Crear cultura'
|
||||
&& $this->matchCategories($categories, ['Cultural'])
|
||||
) {
|
||||
$themes[] = $this->getTheme('culture');
|
||||
}
|
||||
|
||||
if (
|
||||
$socialCommitmentName === 'Acción por el clima'
|
||||
&& $this->matchCategories($categories, ['Ecológico'])
|
||||
) {
|
||||
$themes[] = $this->getTheme('sustainability');
|
||||
}
|
||||
|
||||
if (
|
||||
$socialCommitmentName === 'Datos abiertos'
|
||||
&& $this->matchCategories($categories, ['Científico', 'Comunicativo'])
|
||||
) {
|
||||
$themes[] = $this->getTheme('journalism');
|
||||
}
|
||||
|
||||
if (
|
||||
$socialCommitmentName === 'Reforzar valores democráticos'
|
||||
&& $this->matchCategories($categories, ['Social'])
|
||||
) {
|
||||
$themes[] = $this->getTheme('democracy');
|
||||
}
|
||||
|
||||
if (
|
||||
$socialCommitmentName === 'Igualdad de Género'
|
||||
&& $this->matchCategories($categories, ['Social'])
|
||||
) {
|
||||
$themes[] = $this->getTheme('democracy');
|
||||
}
|
||||
|
||||
if (
|
||||
$socialCommitmentName === 'Salud y Cuidados'
|
||||
&& $this->matchCategories($categories, ['Social'])
|
||||
) {
|
||||
$themes[] = $this->getTheme('health-and-cares');
|
||||
}
|
||||
|
||||
if (
|
||||
$socialCommitmentName === 'Energía y sostenibilidad'
|
||||
&& $this->matchCategories($categories, ['Científico', 'Ecológico'])
|
||||
) {
|
||||
$themes[] = $this->getTheme('science-and-tech');
|
||||
}
|
||||
|
||||
if (
|
||||
$socialCommitmentName === 'Desarrollo agrorural'
|
||||
&& $this->matchCategories($categories, ['Ecológico', 'Emprendedor'])
|
||||
) {
|
||||
$themes[] = $this->getTheme('rural-and-ecology');
|
||||
}
|
||||
|
||||
return $themes;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Benzina;
|
||||
|
||||
use Doctrine\DBAL\Logging\Middleware as LoggingMiddleware;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Gedmo\Loggable\LoggableListener;
|
||||
use Goteo\Benzina\Pump\DoctrinePumpTrait;
|
||||
use Symfony\Contracts\Service\Attribute\Required;
|
||||
|
||||
trait DoctrineLoggablePumpTrait
|
||||
{
|
||||
use DoctrinePumpTrait;
|
||||
|
||||
#[Required()]
|
||||
public function removeLoggable(EntityManagerInterface $entityManager): void
|
||||
{
|
||||
$middlewares = $entityManager->getConnection()->getConfiguration()->getMiddlewares();
|
||||
$middlewares = \array_filter($middlewares, fn($m) => !$m instanceof LoggingMiddleware);
|
||||
|
||||
$entityManager->getConnection()->getConfiguration()->setMiddlewares($middlewares);
|
||||
|
||||
$evm = $entityManager->getEventManager();
|
||||
foreach ($evm->getAllListeners() as $event => $listeners) {
|
||||
foreach ($listeners as $listener) {
|
||||
if ($listener instanceof LoggableListener) {
|
||||
$evm->removeEventListener([$event], $listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->setEntityManager($entityManager);
|
||||
}
|
||||
}
|
||||
@@ -2,25 +2,24 @@
|
||||
|
||||
namespace App\Benzina;
|
||||
|
||||
use App\Entity\Address;
|
||||
use App\Entity\Gateway\Charge;
|
||||
use App\Entity\Project\Reward;
|
||||
use App\Entity\Project\RewardClaim;
|
||||
use App\Entity\Project\RewardClaimStatus;
|
||||
use App\Entity\User\User;
|
||||
use App\Gateway\ChargeStatus;
|
||||
use App\Money\MoneyService;
|
||||
use App\Repository\Gateway\ChargeRepository;
|
||||
use App\Repository\Project\RewardRepository;
|
||||
use App\Repository\User\UserRepository;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use App\Service\UserService;
|
||||
use Goteo\Benzina\Pump\ArrayPumpTrait;
|
||||
use Goteo\Benzina\Pump\DoctrinePumpTrait;
|
||||
use Goteo\Benzina\Pump\PumpInterface;
|
||||
|
||||
class InvestRewardsPump implements PumpInterface
|
||||
{
|
||||
use ArrayPumpTrait;
|
||||
use DatabasePumpTrait;
|
||||
use DoctrinePumpTrait;
|
||||
use DoctrineLoggablePumpTrait;
|
||||
use InvestsPumpTrait;
|
||||
|
||||
/** @var array<string, int> */
|
||||
@@ -34,10 +33,11 @@ class InvestRewardsPump implements PumpInterface
|
||||
|
||||
public function __construct(
|
||||
private RewardRepository $rewardRepository,
|
||||
private UserRepository $userRepository,
|
||||
private PumpedUserRepository $userRepository,
|
||||
private ChargeRepository $chargeRepository,
|
||||
private MoneyService $moneyService,
|
||||
) {}
|
||||
) {
|
||||
$this->setFlushBatchSize(8);
|
||||
}
|
||||
|
||||
public function supports(mixed $sample): bool
|
||||
{
|
||||
@@ -81,6 +81,16 @@ class InvestRewardsPump implements PumpInterface
|
||||
$reward->addClaim($claim);
|
||||
$claim->setReward($reward);
|
||||
|
||||
$status = match ($record['fulfilled']) {
|
||||
0 => RewardClaimStatus::InPending,
|
||||
1 => RewardClaimStatus::Fulfilled,
|
||||
};
|
||||
|
||||
$claim->setStatus($status);
|
||||
|
||||
$address = $this->getAddress($claim, $context);
|
||||
$claim->setAddress($address);
|
||||
|
||||
$this->persist($claim, $context);
|
||||
}
|
||||
|
||||
@@ -139,22 +149,34 @@ class InvestRewardsPump implements PumpInterface
|
||||
|
||||
private function getUser(array $record): ?User
|
||||
{
|
||||
$id = $record['user'];
|
||||
return $this->userRepository->findPumped($record['user']);
|
||||
}
|
||||
|
||||
if (isset($this->userCache[$id])) {
|
||||
return $this->userRepository->find($this->userCache[$id]);
|
||||
private function getAddress(RewardClaim $claim, array $context): ?Address
|
||||
{
|
||||
$query = $this->getDbConnection($context)->prepare(
|
||||
'SELECT * FROM `invest_address` a WHERE a.invest = :invest'
|
||||
);
|
||||
|
||||
$query->execute(['invest' => $claim->getCharge()->getMigratedId()]);
|
||||
|
||||
$result = $query->fetch(\PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$result || $result['name'] === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$criteria = new Criteria();
|
||||
$criteria
|
||||
->orWhere($criteria->expr()->eq('migratedId', $id))
|
||||
->orWhere($criteria->expr()->contains('dedupedIds', $id))
|
||||
->setMaxResults(1);
|
||||
[$firstName, $lastName] = UserService::guessNames($result['name']);
|
||||
|
||||
$user = $this->userRepository->matching($criteria)->first();
|
||||
$address = new Address();
|
||||
$address->setUser($claim->getOwner());
|
||||
$address->setFirstName($firstName);
|
||||
$address->setLastName($lastName);
|
||||
$address->setLine1($result['address']);
|
||||
$address->setCity($result['location']);
|
||||
$address->setPostCode($result['zipcode']);
|
||||
$address->setCountry($result['country']);
|
||||
|
||||
$this->userCache[$id] = $user->getId();
|
||||
|
||||
return $user;
|
||||
return $address;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,16 +27,14 @@ use App\Repository\Project\ProjectRepository;
|
||||
use App\Repository\Project\RewardRepository;
|
||||
use App\Repository\Project\SupportRepository;
|
||||
use App\Repository\TipjarRepository;
|
||||
use App\Service\Gateway\CheckoutService;
|
||||
use Goteo\Benzina\Pump\ArrayPumpTrait;
|
||||
use Goteo\Benzina\Pump\DoctrinePumpTrait;
|
||||
use Goteo\Benzina\Pump\PumpInterface;
|
||||
|
||||
class InvestsPump implements PumpInterface
|
||||
{
|
||||
use ArrayPumpTrait;
|
||||
use DatabasePumpTrait;
|
||||
use DoctrinePumpTrait;
|
||||
use DoctrineLoggablePumpTrait;
|
||||
use InvestsPumpTrait;
|
||||
|
||||
public const TRACKING_TITLE_V3 = 'v3 Invest ID';
|
||||
@@ -71,9 +69,10 @@ class InvestsPump implements PumpInterface
|
||||
private SupportRepository $supportRepository,
|
||||
private TipjarRepository $tipjarRepository,
|
||||
private RewardRepository $rewardRepository,
|
||||
private CheckoutService $checkoutService,
|
||||
private MoneyService $moneyService,
|
||||
) {}
|
||||
) {
|
||||
$this->setFlushBatchSize(1);
|
||||
}
|
||||
|
||||
public function supports(mixed $sample): bool
|
||||
{
|
||||
@@ -86,6 +85,10 @@ class InvestsPump implements PumpInterface
|
||||
|
||||
public function pump(mixed $record, array $context): void
|
||||
{
|
||||
if (!in_array($record['project'], ProjectsPump::SELECTED_PROJECTS)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$record['user'] || empty($record['user'])) {
|
||||
return;
|
||||
}
|
||||
@@ -261,7 +264,7 @@ class InvestsPump implements PumpInterface
|
||||
$originId = $origin->getId();
|
||||
$projectId = $target->getId();
|
||||
|
||||
$cacheKey = $projectId.'-'.$originId;
|
||||
$cacheKey = $projectId . '-' . $originId;
|
||||
|
||||
if (isset($this->supportCache[$cacheKey])) {
|
||||
return $this->supportRepository->find($this->supportCache[$cacheKey]);
|
||||
|
||||
@@ -9,14 +9,13 @@ use App\Entity\Project\Project;
|
||||
use App\Entity\Project\ProjectDeadline;
|
||||
use App\Repository\Project\ProjectRepository;
|
||||
use Goteo\Benzina\Pump\ArrayPumpTrait;
|
||||
use Goteo\Benzina\Pump\DoctrinePumpTrait;
|
||||
use Goteo\Benzina\Pump\PumpInterface;
|
||||
|
||||
class ProjectsBudgetPump implements PumpInterface
|
||||
{
|
||||
use ArrayPumpTrait;
|
||||
use DatabasePumpTrait;
|
||||
use DoctrinePumpTrait;
|
||||
use DoctrineLoggablePumpTrait;
|
||||
use LocalizedPumpTrait;
|
||||
|
||||
private const MAX_INT = 2147483647;
|
||||
@@ -49,6 +48,10 @@ class ProjectsBudgetPump implements PumpInterface
|
||||
|
||||
public function pump(mixed $record, array $context): void
|
||||
{
|
||||
if (!in_array($record['project'], ProjectsPump::SELECTED_PROJECTS)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (empty($record['cost']) || empty($record['amount'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
+126
-17
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Benzina;
|
||||
|
||||
use App\Entity\Project\Collaboration;
|
||||
use App\Entity\Project\Project;
|
||||
use App\Entity\Project\ProjectCalendar;
|
||||
use App\Entity\Project\ProjectDeadline;
|
||||
@@ -11,28 +12,49 @@ use App\Entity\Project\Update;
|
||||
use App\Entity\Territory;
|
||||
use App\Entity\User\User;
|
||||
use App\Repository\Project\ProjectRepository;
|
||||
use App\Repository\User\UserRepository;
|
||||
use App\Service\Project\TerritoryService;
|
||||
use App\Service\Scout\FileUriException;
|
||||
use App\Service\Scout\NonCrawlableUriException;
|
||||
use App\Service\Scout\ScoutService;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Goteo\Benzina\Pump\ArrayPumpTrait;
|
||||
use Goteo\Benzina\Pump\DoctrinePumpTrait;
|
||||
use Goteo\Benzina\Pump\PumpInterface;
|
||||
|
||||
class ProjectsPump implements PumpInterface
|
||||
{
|
||||
use ArrayPumpTrait;
|
||||
use DoctrinePumpTrait;
|
||||
use DoctrineLoggablePumpTrait;
|
||||
use DatabasePumpTrait;
|
||||
use ProjectsPumpTrait;
|
||||
use LocalizedPumpTrait;
|
||||
use TerritoryPumpTrait;
|
||||
use CategorizationPumpTrait;
|
||||
|
||||
public const SELECTED_PROJECTS = [
|
||||
'la-nuestra-despega',
|
||||
'la-nuestra',
|
||||
'un-netflix-para-el-99-decenas-de-creadores-y-cinea',
|
||||
'iridia-vs-pegasus',
|
||||
'impulsa-la-cooperativa-de-informacion-climatica',
|
||||
'amb-memoria-drets-humans',
|
||||
'ara-drets-humans',
|
||||
'amb-memoria-drets-humans',
|
||||
'quien-cuida-a-las-que-cuidan',
|
||||
'con-vosotras-si-campana-de-financiacion-duque-de-a',
|
||||
'top-manta-bcn',
|
||||
'salvem-el-delta-del-llobregat',
|
||||
'refugio-una-huida-para-sobrevivir',
|
||||
'plokte',
|
||||
'lanzamos-sonda-internacional-y-los-cinco-elementos',
|
||||
'la-kacharreria',
|
||||
'portal-de-ressenyes-sobre-pisos',
|
||||
'indicat-de-llogaters-i-llogateres/',
|
||||
'jornaleras',
|
||||
'las-kellys'
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
private ProjectRepository $projectRepository,
|
||||
private UserRepository $userRepository,
|
||||
private PumpedUserRepository $userRepository,
|
||||
private TerritoryService $territoryService,
|
||||
private ScoutService $scoutService,
|
||||
) {}
|
||||
@@ -48,7 +70,7 @@ class ProjectsPump implements PumpInterface
|
||||
|
||||
public function pump(mixed $record, array $context): void
|
||||
{
|
||||
if (empty($record['name'])) {
|
||||
if (empty($record['name']) || !in_array($record['id'], self::SELECTED_PROJECTS)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -82,11 +104,18 @@ class ProjectsPump implements PumpInterface
|
||||
$project->setDateUpdated(new \DateTime());
|
||||
$project->setTranslatableLocale($record['lang']);
|
||||
$project->setUpdates(new ArrayCollection($this->getProjectUpdates($project, $context)));
|
||||
$project->setCollaborations(new ArrayCollection($this->getProjectCollaborations($project, $context)));
|
||||
|
||||
$video = $this->getProjectVideo($record);
|
||||
if ($video !== null) {
|
||||
$project->setVideo($video);
|
||||
$project->setCover($video->cover);
|
||||
}
|
||||
|
||||
$project->setVideo($video);
|
||||
$project->setCover($video->cover);
|
||||
$cover = $this->getProjectCover($record, $context);
|
||||
if ($cover !== null) {
|
||||
$project->setCover($cover);
|
||||
}
|
||||
|
||||
$conf = $this->getProjectConf($project, $context);
|
||||
|
||||
@@ -101,6 +130,11 @@ class ProjectsPump implements PumpInterface
|
||||
$project->setDescGoal($record['motivation']);
|
||||
$project->setDescTeam($record['related']);
|
||||
|
||||
$themes = $this->getProjectThemes($record, $context);
|
||||
foreach ($themes as $theme) {
|
||||
$project->addTheme($theme);
|
||||
}
|
||||
|
||||
$this->setPreventFlushAndClear(true);
|
||||
$this->persist($project, $context);
|
||||
|
||||
@@ -124,13 +158,7 @@ class ProjectsPump implements PumpInterface
|
||||
|
||||
private function getProjectOwner(array $record): ?User
|
||||
{
|
||||
$criteria = new Criteria();
|
||||
$criteria
|
||||
->orWhere($criteria->expr()->eq('migratedId', $record['owner']))
|
||||
->orWhere($criteria->expr()->contains('dedupedIds', $record['owner']))
|
||||
->setMaxResults(1);
|
||||
|
||||
return $this->userRepository->matching($criteria)->first() ?? null;
|
||||
return $this->userRepository->findPumped($record['owner']);
|
||||
}
|
||||
|
||||
private function getProjectLocalizations(Project $project, array $context): array
|
||||
@@ -240,7 +268,7 @@ class ProjectsPump implements PumpInterface
|
||||
}
|
||||
|
||||
return new ProjectVideo($info->url, $info->cover ?? $info->image, $info->image);
|
||||
} catch (FileUriException $e) {
|
||||
} catch (NonCrawlableUriException $e) {
|
||||
return new ProjectVideo($e->getUri());
|
||||
} catch (\Exception $e) {
|
||||
return null;
|
||||
@@ -346,4 +374,85 @@ class ProjectsPump implements PumpInterface
|
||||
|
||||
return ProjectDeadline::Minimum;
|
||||
}
|
||||
|
||||
private function getImageSource(array $record, array $context): ?string
|
||||
{
|
||||
$query = $this->getDbConnection($context)->prepare(
|
||||
'SELECT * FROM `project_image` WHERE `project` = :project ORDER BY `order` ASC'
|
||||
);
|
||||
|
||||
$query->execute(['project' => $record['id']]);
|
||||
|
||||
$images = $query->fetchAll();
|
||||
|
||||
foreach ($images as $image) {
|
||||
if ($image['section'] === 'play-video') {
|
||||
return $image['image'];
|
||||
}
|
||||
}
|
||||
|
||||
return $record['image'];
|
||||
}
|
||||
|
||||
private function getProjectCover(array $record, array $context): ?string
|
||||
{
|
||||
$image = $this->getImageSource($record, $context);
|
||||
|
||||
if ($image === null || $image === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!\str_contains($image, '.')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return \sprintf('https://s3.eu-west-1.amazonaws.com/goteoassets.org/images/%s', $image);
|
||||
}
|
||||
|
||||
private function getProjectCollaborations(Project $project, array $context): array
|
||||
{
|
||||
$collaborations = [];
|
||||
|
||||
$query = $this->getDbConnection($context)->prepare(
|
||||
'SELECT * FROM `support` s WHERE s.project = :project'
|
||||
);
|
||||
|
||||
$query->execute(['project' => $project->getMigratedId()]);
|
||||
$supports = $query->fetchAll();
|
||||
|
||||
foreach ($supports as $support) {
|
||||
$fulfilled = in_array($project->getStatus(), [
|
||||
ProjectStatus::FundingPaid,
|
||||
]);
|
||||
|
||||
$collaboration = new Collaboration();
|
||||
$collaboration->setTitle($support['support']);
|
||||
$collaboration->setDescription($support['description']);
|
||||
$collaboration->setFulfilled($fulfilled);
|
||||
|
||||
$collaborations[] = $collaboration;
|
||||
$project->addCollaboration($collaboration);
|
||||
}
|
||||
|
||||
return $collaborations;
|
||||
}
|
||||
|
||||
private function getProjectThemes(array $record, array $context): array
|
||||
{
|
||||
$scQuery = $this->getDbConnection($context)->prepare(
|
||||
'SELECT * FROM `social_commitment` sc WHERE sc.id = :scId'
|
||||
);
|
||||
|
||||
$scQuery->execute(['scId' => $record['social_commitment']]);
|
||||
$socialCommitment = $scQuery->fetch();
|
||||
|
||||
$catsQuery = $this->getDbConnection($context)->prepare(
|
||||
'SELECT * FROM `category` c INNER JOIN `project_category` pc ON c.id = pc.category WHERE pc.project = :project'
|
||||
);
|
||||
|
||||
$catsQuery->execute(['project' => $record['id']]);
|
||||
$categories = $catsQuery->fetchAll();
|
||||
|
||||
return $this->mapOldCategoriesToThemes($socialCommitment['name'] ?? '', $categories);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,14 +8,13 @@ use App\Entity\Project\Project;
|
||||
use App\Entity\Project\Reward;
|
||||
use App\Repository\Project\ProjectRepository;
|
||||
use Goteo\Benzina\Pump\ArrayPumpTrait;
|
||||
use Goteo\Benzina\Pump\DoctrinePumpTrait;
|
||||
use Goteo\Benzina\Pump\PumpInterface;
|
||||
|
||||
class ProjectsRewardsPump implements PumpInterface
|
||||
{
|
||||
use ArrayPumpTrait;
|
||||
use DatabasePumpTrait;
|
||||
use DoctrinePumpTrait;
|
||||
use DoctrineLoggablePumpTrait;
|
||||
use LocalizedPumpTrait;
|
||||
|
||||
private const REWARD_KEYS = [
|
||||
@@ -53,6 +52,10 @@ class ProjectsRewardsPump implements PumpInterface
|
||||
|
||||
public function pump(mixed $record, array $context): void
|
||||
{
|
||||
if (!in_array($record['project'], ProjectsPump::SELECTED_PROJECTS)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (empty($record['reward']) || empty($record['amount'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
+54
-10
@@ -12,7 +12,6 @@ use App\Service\Project\TerritoryService;
|
||||
use App\Service\UserService;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
use Goteo\Benzina\Pump\ArrayPumpTrait;
|
||||
use Goteo\Benzina\Pump\DoctrinePumpTrait;
|
||||
use Goteo\Benzina\Pump\PumpInterface;
|
||||
use Symfony\Component\Validator\Constraints\Url;
|
||||
use Symfony\Component\Validator\Validation;
|
||||
@@ -20,14 +19,17 @@ use Symfony\Component\Validator\Validation;
|
||||
class UsersPump implements PumpInterface
|
||||
{
|
||||
use ArrayPumpTrait;
|
||||
use DoctrinePumpTrait;
|
||||
use DoctrineLoggablePumpTrait;
|
||||
use UsersPumpTrait;
|
||||
use TerritoryPumpTrait;
|
||||
use DatabasePumpTrait;
|
||||
|
||||
public function __construct(
|
||||
private ManagerRegistry $managerRegistry,
|
||||
private TerritoryService $territoryService,
|
||||
) {}
|
||||
) {
|
||||
$this->setFlushBatchSize(8);
|
||||
}
|
||||
|
||||
public function supports(mixed $sample): bool
|
||||
{
|
||||
@@ -41,7 +43,7 @@ class UsersPump implements PumpInterface
|
||||
public function pump(mixed $record, array $context): void
|
||||
{
|
||||
$user = new User();
|
||||
$user = $this->processUser($user, $record);
|
||||
$user = $this->processUser($user, $record, $context);
|
||||
|
||||
try {
|
||||
$this->persist($user, $context);
|
||||
@@ -62,7 +64,7 @@ class UsersPump implements PumpInterface
|
||||
}
|
||||
|
||||
$user = new User();
|
||||
$user = $this->processUser($user, $record);
|
||||
$user = $this->processUser($user, $record, $context);
|
||||
$user->setHandle(UserService::asHandle($record['id'], 16, 255));
|
||||
|
||||
$this->persist($user, $context);
|
||||
@@ -71,11 +73,12 @@ class UsersPump implements PumpInterface
|
||||
}
|
||||
}
|
||||
|
||||
private function processUser(User $user, array $record): User
|
||||
private function processUser(User $user, array $record, array $context): User
|
||||
{
|
||||
$user->setHandle($this->buildHandle($record));
|
||||
$handle = hash('sha256', $record['email']);
|
||||
$user->setHandle($handle);
|
||||
$user->setPassword($record['password'] ?? '');
|
||||
$user->setEmail($record['email']);
|
||||
$user->setEmail(\sprintf('%s@goteo.org', $handle));
|
||||
$user->setEmailConfirmed(false);
|
||||
$user->setActive(false);
|
||||
$user->setMigrated(true);
|
||||
@@ -83,9 +86,11 @@ class UsersPump implements PumpInterface
|
||||
$user->setDateCreated($this->getDateCreated($record));
|
||||
$user->setDateUpdated(new \DateTime());
|
||||
$user->setType($this->getUserType($record));
|
||||
$user->setLinks($this->getLinks($record));
|
||||
// $user->setLinks($this->getLinks($record));
|
||||
$user->setTerritory($this->getTerritory($record));
|
||||
$user->setDescription($record['about']);
|
||||
// $user->setDescription($record['about']);
|
||||
$user->setRoles($this->getRoles($record, $context));
|
||||
// $user->setAvatar($this->getAvatar($record));
|
||||
|
||||
match ($user->getType()) {
|
||||
UserType::Individual => $user = $this->setUserPerson($record, $user),
|
||||
@@ -204,4 +209,43 @@ class UsersPump implements PumpInterface
|
||||
|
||||
return $this->territoryService->search($cleanAddress);
|
||||
}
|
||||
|
||||
private function getRoles(array $record, array $context): array
|
||||
{
|
||||
$query = $this->getDbConnection($context)->prepare(
|
||||
'SELECT * FROM `user_role` r WHERE r.user_id = :user'
|
||||
);
|
||||
|
||||
$query->execute(['user' => $record['id']]);
|
||||
|
||||
$results = $query->fetchAll(\PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$results || empty($results)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$roles = [];
|
||||
foreach ($results as $result) {
|
||||
if (in_array($result['role_id'], ['superadmin', 'manager'])) {
|
||||
$roles[] = 'ROLE_ADMIN';
|
||||
}
|
||||
}
|
||||
|
||||
return $roles;
|
||||
}
|
||||
|
||||
private function getAvatar(array $record): ?string
|
||||
{
|
||||
$image = $record['avatar'];
|
||||
|
||||
if ($image === null || $image === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!\str_contains($image, '.')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return \sprintf('https://s3.eu-west-1.amazonaws.com/goteoassets.org/images/%s', $image);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ class OAuth2ConsentClientCommand extends Command
|
||||
$this
|
||||
->setDescription('Consents an OAuth2 client, allowing it to skip the consent screen')
|
||||
|
||||
->addOption('consent', null, InputOption::VALUE_NEGATABLE, 'Consent the client')
|
||||
->addOption('consent', null, InputOption::VALUE_NEGATABLE, 'Consent the client', true)
|
||||
|
||||
->addArgument('identifier', InputArgument::REQUIRED, 'The client identifier')
|
||||
;
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Dto;
|
||||
|
||||
trait CategorizedInputDtoTrait
|
||||
{
|
||||
public const array CATEGORIES_OPENAPI_CONTEXT = [
|
||||
'type' => 'array',
|
||||
'items' => [
|
||||
'type' => 'string',
|
||||
'format' => 'iri-reference',
|
||||
'example' => '/categories/cooperativism',
|
||||
],
|
||||
'example' => [
|
||||
'/categories/open-source-software',
|
||||
'/categories/cooperativism',
|
||||
],
|
||||
];
|
||||
|
||||
public const array THEMES_OPENAPI_CONTEXT = [
|
||||
'type' => 'array',
|
||||
'items' => [
|
||||
'type' => 'string',
|
||||
'format' => 'iri-reference',
|
||||
'example' => '/themes/music',
|
||||
],
|
||||
'example' => [
|
||||
'/themes/music',
|
||||
'/themes/art',
|
||||
],
|
||||
];
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Dto;
|
||||
|
||||
trait CategoryInputDtoTrait
|
||||
{
|
||||
public const array CATEGORIES_OPENAPI_CONTEXT = [
|
||||
'type' => 'array',
|
||||
'items' => [
|
||||
'type' => 'string',
|
||||
'format' => 'iri-reference',
|
||||
'example' => '/categories/music',
|
||||
],
|
||||
'example' => [
|
||||
'/categories/music',
|
||||
'/categories/art',
|
||||
],
|
||||
];
|
||||
}
|
||||
@@ -4,14 +4,17 @@ namespace App\Dto;
|
||||
|
||||
use ApiPlatform\Metadata as API;
|
||||
use App\ApiResource\CategoryApiResource;
|
||||
use App\ApiResource\ThemeApiResource;
|
||||
use App\Entity\Project\Project;
|
||||
use App\Entity\Project\ProjectCalendar;
|
||||
use App\Entity\Project\ProjectStatus;
|
||||
use App\Entity\Territory;
|
||||
use App\Validator\NotExisting;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
class ProjectCreationDto
|
||||
{
|
||||
use CategoryInputDtoTrait;
|
||||
use CategorizedInputDtoTrait;
|
||||
|
||||
/**
|
||||
* Main headline for the Project. Must include at least one character between a-Z.
|
||||
@@ -19,6 +22,7 @@ class ProjectCreationDto
|
||||
#[Assert\NotBlank()]
|
||||
#[Assert\Regex('/[a-zA-Z]{1,}/')]
|
||||
#[Assert\Length(min: 3)]
|
||||
#[NotExisting(Project::class, 'title')]
|
||||
public string $title;
|
||||
|
||||
/**
|
||||
@@ -33,10 +37,19 @@ class ProjectCreationDto
|
||||
* @var CategoryApiResource[]
|
||||
*/
|
||||
#[Assert\NotBlank()]
|
||||
#[Assert\Count(min: 1, max: 2)]
|
||||
#[Assert\Count(exactly: 1)]
|
||||
#[API\ApiProperty(writableLink: false, openapiContext: self::CATEGORIES_OPENAPI_CONTEXT)]
|
||||
public array $categories;
|
||||
|
||||
/**
|
||||
* List of Themes.
|
||||
*
|
||||
* @var ThemeApiResource[]
|
||||
*/
|
||||
#[Assert\Count(min: 1, max: 2)]
|
||||
#[API\ApiProperty(writableLink: false, openapiContext: self::THEMES_OPENAPI_CONTEXT)]
|
||||
public array $themes;
|
||||
|
||||
/**
|
||||
* ISO 3166 data about the Project's territory of interest.
|
||||
*/
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Dto;
|
||||
|
||||
use ApiPlatform\Metadata as API;
|
||||
use App\ApiResource\CategoryApiResource;
|
||||
use App\ApiResource\ThemeApiResource;
|
||||
use App\Entity\Project\Project;
|
||||
use App\Entity\Project\ProjectCalendar;
|
||||
use App\Entity\Project\ProjectDeadline;
|
||||
@@ -15,7 +16,7 @@ use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
class ProjectUpdationDto
|
||||
{
|
||||
use CategoryInputDtoTrait;
|
||||
use CategorizedInputDtoTrait;
|
||||
|
||||
#[API\ApiProperty(identifier: true, writable: false)]
|
||||
public int $id;
|
||||
@@ -47,6 +48,15 @@ class ProjectUpdationDto
|
||||
#[API\ApiProperty(writableLink: false, openapiContext: self::CATEGORIES_OPENAPI_CONTEXT)]
|
||||
public array $categories;
|
||||
|
||||
/**
|
||||
* List of Themes.
|
||||
*
|
||||
* @var ThemeApiResource[]
|
||||
*/
|
||||
#[Assert\Count(max: 1)]
|
||||
#[API\ApiProperty(writableLink: false, openapiContext: self::THEMES_OPENAPI_CONTEXT)]
|
||||
public array $themes;
|
||||
|
||||
/**
|
||||
* ISO 3166 data about the Project's territory of interest.
|
||||
*/
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Dto;
|
||||
|
||||
use App\ApiResource\AddressApiResource;
|
||||
use App\ApiResource\Gateway\ChargeApiResource;
|
||||
use App\ApiResource\Project\RewardApiResource;
|
||||
use App\Validator\AvailableRewardUnits;
|
||||
@@ -27,4 +28,9 @@ class RewardClaimCreationDto
|
||||
#[Assert\NotBlank()]
|
||||
#[AvailableRewardUnits()]
|
||||
public RewardApiResource $reward;
|
||||
|
||||
/**
|
||||
* If the reward is a physical object that needs to be delivered to an specific place.
|
||||
*/
|
||||
public ?AddressApiResource $address;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Dto;
|
||||
|
||||
use ApiPlatform\Metadata as API;
|
||||
use App\ApiResource\AddressApiResource;
|
||||
use App\Entity\Project\RewardClaimStatus;
|
||||
|
||||
class RewardClaimUpdationDto
|
||||
{
|
||||
/**
|
||||
* The point at which the claim over the reward is.\
|
||||
* May only be updated by admins or the User who owns the Project of the ProjectReward.
|
||||
*/
|
||||
#[API\ApiProperty(security: 'is_granted("CLAIM_EDIT", object)')]
|
||||
public RewardClaimStatus $status;
|
||||
|
||||
/**
|
||||
* If the reward is a physical object that needs to be delivered to an specific place.
|
||||
*/
|
||||
#[API\ApiProperty(security: 'is_granted("CLAIM_OWNS", object)')]
|
||||
public ?AddressApiResource $address;
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Dto;
|
||||
namespace App\Dto\User;
|
||||
|
||||
use App\Entity\User\User;
|
||||
use App\Entity\User\UserType;
|
||||
use App\Validator\NotExisting;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
final class UserSignupDto
|
||||
@@ -12,10 +14,11 @@ final class UserSignupDto
|
||||
*/
|
||||
#[Assert\NotBlank()]
|
||||
#[Assert\Email()]
|
||||
#[NotExisting(User::class, 'email')]
|
||||
public string $email;
|
||||
|
||||
/**
|
||||
* The auth password for the new User. Plaintext string,
|
||||
* The authentication password for the new User. Plaintext string,
|
||||
* will be hashed by the API.
|
||||
*/
|
||||
#[Assert\NotBlank()]
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace App\Dto\User;
|
||||
|
||||
use ApiPlatform\Metadata as API;
|
||||
use App\ApiResource\User\UserApiResource;
|
||||
use App\Entity\Territory;
|
||||
use App\Entity\User\User;
|
||||
use App\Entity\User\UserType;
|
||||
use App\Mapping\Transformer\RawLinksMapTransformer;
|
||||
use App\Validator\NotExisting;
|
||||
use AutoMapper\Attribute\MapTo;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
final class UserUpdationDto
|
||||
{
|
||||
/**
|
||||
* A unique, non white space, byte-safe string identifier for this User.
|
||||
*/
|
||||
#[Assert\Length(min: 4, max: 30)]
|
||||
#[Assert\Regex('/^[a-z0-9_]+$/')]
|
||||
#[NotExisting(User::class, 'handle')]
|
||||
public string $handle;
|
||||
|
||||
/**
|
||||
* The User's given email address. Only available to themselves and platform administrators.
|
||||
*/
|
||||
#[Assert\Email()]
|
||||
#[NotExisting(User::class, 'email')]
|
||||
public string $email;
|
||||
|
||||
/**
|
||||
* The authentication password for the User. Plaintext string,
|
||||
* will be hashed by the API.
|
||||
*/
|
||||
#[Assert\Length(min: 8)]
|
||||
public string $password;
|
||||
|
||||
/**
|
||||
* URL to the avatar image of this User.
|
||||
*/
|
||||
#[Assert\Url()]
|
||||
public string $avatar;
|
||||
|
||||
/**
|
||||
* Is this User for an individual acting on their own or a group of individuals?
|
||||
*/
|
||||
public UserType $type;
|
||||
|
||||
/**
|
||||
* A list of the roles assigned to this User. Admin scoped property.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
#[API\ApiProperty(securityPostDenormalize: 'is_granted("ROLE_ADMIN")')]
|
||||
public array $roles;
|
||||
|
||||
/**
|
||||
* A list of absolute URLs.\
|
||||
* e.g: social profiles, personal website.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
#[MapTo(User::class, transformer: RawLinksMapTransformer::class)]
|
||||
#[MapTo(UserApiResource::class, transformer: RawLinksMapTransformer::class)]
|
||||
public array $links = [];
|
||||
|
||||
/**
|
||||
* ISO 3166 data about the Users's location territory.
|
||||
*/
|
||||
#[Assert\Valid()]
|
||||
public Territory $territory;
|
||||
|
||||
/**
|
||||
* Free-form rich text description for the User.
|
||||
*/
|
||||
public string $description;
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Entity\User\User;
|
||||
use App\Repository\AddressRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity(repositoryClass: AddressRepository::class)]
|
||||
class Address
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\Column(length: 255)]
|
||||
private ?string $firstName = null;
|
||||
|
||||
#[ORM\Column(length: 255)]
|
||||
private ?string $lastName = null;
|
||||
|
||||
#[ORM\Column(length: 255)]
|
||||
private ?string $line1 = null;
|
||||
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
private ?string $line2 = null;
|
||||
|
||||
#[ORM\Column(length: 255)]
|
||||
private ?string $city = null;
|
||||
|
||||
#[ORM\Column(length: 255)]
|
||||
private ?string $postCode = null;
|
||||
|
||||
#[ORM\Column(length: 2)]
|
||||
private ?string $country = null;
|
||||
|
||||
#[ORM\ManyToOne(inversedBy: 'addresses')]
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
private ?User $user = null;
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getFirstName(): ?string
|
||||
{
|
||||
return $this->firstName;
|
||||
}
|
||||
|
||||
public function setFirstName(string $firstName): static
|
||||
{
|
||||
$this->firstName = $firstName;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getLastName(): ?string
|
||||
{
|
||||
return $this->lastName;
|
||||
}
|
||||
|
||||
public function setLastName(string $lastName): static
|
||||
{
|
||||
$this->lastName = $lastName;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getLine1(): ?string
|
||||
{
|
||||
return $this->line1;
|
||||
}
|
||||
|
||||
public function setLine1(string $line1): static
|
||||
{
|
||||
$this->line1 = $line1;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getLine2(): ?string
|
||||
{
|
||||
return $this->line2;
|
||||
}
|
||||
|
||||
public function setLine2(?string $line2): static
|
||||
{
|
||||
$this->line2 = $line2;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCity(): ?string
|
||||
{
|
||||
return $this->city;
|
||||
}
|
||||
|
||||
public function setCity(string $city): static
|
||||
{
|
||||
$this->city = $city;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getPostCode(): ?string
|
||||
{
|
||||
return $this->postCode;
|
||||
}
|
||||
|
||||
public function setPostCode(string $postCode): static
|
||||
{
|
||||
$this->postCode = $postCode;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCountry(): ?string
|
||||
{
|
||||
return $this->country;
|
||||
}
|
||||
|
||||
public function setCountry(string $country): static
|
||||
{
|
||||
$this->country = $country;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getUser(): ?User
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
public function setUser(?User $user): static
|
||||
{
|
||||
$this->user = $user;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ use App\Entity\Matchfunding\MatchCallSubmission;
|
||||
use App\Entity\Matchfunding\MatchCallSubmissionStatus;
|
||||
use App\Entity\MigratedTrait;
|
||||
use App\Entity\Territory;
|
||||
use App\Entity\Theme;
|
||||
use App\Entity\User\User;
|
||||
use App\Entity\UserOwnedInterface;
|
||||
use App\Entity\UserOwnedTrait;
|
||||
@@ -157,10 +158,16 @@ class Project implements UserOwnedInterface, AccountingOwnerInterface, Localized
|
||||
/**
|
||||
* @var Collection<int, Collaboration>
|
||||
*/
|
||||
#[ORM\OneToMany(targetEntity: Collaboration::class, mappedBy: 'project')]
|
||||
#[ORM\OneToMany(targetEntity: Collaboration::class, mappedBy: 'project', cascade: ['persist'])]
|
||||
private Collection $collaborations;
|
||||
|
||||
/**
|
||||
* @var Collection<int, Review>
|
||||
*/
|
||||
#[ORM\OneToMany(targetEntity: Review::class, mappedBy: 'project', cascade: ['persist'])]
|
||||
private Collection $reviews;
|
||||
|
||||
/*
|
||||
* A list of URLs provided by the Project owner.\
|
||||
* e.g: social profiles, project website.
|
||||
*
|
||||
@@ -169,6 +176,12 @@ class Project implements UserOwnedInterface, AccountingOwnerInterface, Localized
|
||||
#[ORM\Column(nullable: true)]
|
||||
private ?array $links = null;
|
||||
|
||||
/**
|
||||
* @var Collection<int, Theme>
|
||||
*/
|
||||
#[ORM\ManyToMany(targetEntity: Theme::class, cascade: ['persist'])]
|
||||
private Collection $themes;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->accounting = Accounting::of($this);
|
||||
@@ -179,6 +192,8 @@ class Project implements UserOwnedInterface, AccountingOwnerInterface, Localized
|
||||
$this->supports = new ArrayCollection();
|
||||
$this->categories = new ArrayCollection();
|
||||
$this->collaborations = new ArrayCollection();
|
||||
$this->reviews = new ArrayCollection();
|
||||
$this->themes = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
@@ -378,6 +393,10 @@ class Project implements UserOwnedInterface, AccountingOwnerInterface, Localized
|
||||
return $this->status;
|
||||
}
|
||||
|
||||
/**
|
||||
* DO NOT call this method unless you know what you are doing:
|
||||
* Instead use `ProjectService::transition` or you WILL miss side-effects.
|
||||
*/
|
||||
public function setStatus(ProjectStatus $status): static
|
||||
{
|
||||
$this->status = $status;
|
||||
@@ -561,6 +580,16 @@ class Project implements UserOwnedInterface, AccountingOwnerInterface, Localized
|
||||
return $this->collaborations;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Collection<int, Collaboration> $collaborations
|
||||
*/
|
||||
public function setCollaborations(Collection $collaborations): static
|
||||
{
|
||||
$this->collaborations = $collaborations;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addCollaboration(Collaboration $collaboration): static
|
||||
{
|
||||
if (!$this->collaborations->contains($collaboration)) {
|
||||
@@ -584,6 +613,36 @@ class Project implements UserOwnedInterface, AccountingOwnerInterface, Localized
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, Review>
|
||||
*/
|
||||
public function getReviews(): Collection
|
||||
{
|
||||
return $this->reviews;
|
||||
}
|
||||
|
||||
public function addReview(Review $review): static
|
||||
{
|
||||
if (!$this->reviews->contains($review)) {
|
||||
$this->reviews->add($review);
|
||||
$review->setProject($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeReview(Review $review): static
|
||||
{
|
||||
if ($this->reviews->removeElement($review)) {
|
||||
// set the owning side to null (unless already changed)
|
||||
if ($review->getProject() === $this) {
|
||||
$review->setProject(null);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/*
|
||||
* @return Link[]
|
||||
*/
|
||||
public function getLinks(): ?array
|
||||
@@ -600,4 +659,28 @@ class Project implements UserOwnedInterface, AccountingOwnerInterface, Localized
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, Theme>
|
||||
*/
|
||||
public function getThemes(): Collection
|
||||
{
|
||||
return $this->themes;
|
||||
}
|
||||
|
||||
public function addTheme(Theme $theme): static
|
||||
{
|
||||
if (!$this->themes->contains($theme)) {
|
||||
$this->themes->add($theme);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeTheme(Theme $theme): static
|
||||
{
|
||||
$this->themes->removeElement($theme);
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity\Project;
|
||||
|
||||
use App\Entity\DateCreatedTrait;
|
||||
use App\Entity\DateUpdatedTrait;
|
||||
use App\Entity\User\User;
|
||||
use App\Mapping\Provider\EntityMapProvider;
|
||||
use App\Repository\Project\ReviewRepository;
|
||||
use AutoMapper\Attribute\MapProvider;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
|
||||
#[MapProvider(EntityMapProvider::class)]
|
||||
#[ORM\Entity(repositoryClass: ReviewRepository::class)]
|
||||
class Review
|
||||
{
|
||||
use DateCreatedTrait;
|
||||
use DateUpdatedTrait;
|
||||
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\ManyToOne(inversedBy: 'reviews')]
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
private ?Project $project = null;
|
||||
|
||||
#[ORM\ManyToOne]
|
||||
private ?User $reviewer = null;
|
||||
|
||||
#[ORM\Column(enumType: ReviewType::class)]
|
||||
private ?ReviewType $type = null;
|
||||
|
||||
/**
|
||||
* @var Collection<int, ReviewArea>
|
||||
*/
|
||||
#[ORM\OneToMany(targetEntity: ReviewArea::class, mappedBy: 'review', cascade: ['persist'])]
|
||||
private Collection $areas;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->areas = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getProject(): ?Project
|
||||
{
|
||||
return $this->project;
|
||||
}
|
||||
|
||||
public function setProject(?Project $project): static
|
||||
{
|
||||
$this->project = $project;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getReviewer(): ?User
|
||||
{
|
||||
return $this->reviewer;
|
||||
}
|
||||
|
||||
public function isReviewedBy(UserInterface $user): bool
|
||||
{
|
||||
return $this->reviewer?->getUserIdentifier() === $user->getUserIdentifier();
|
||||
}
|
||||
|
||||
public function setReviewer(?User $reviewer): static
|
||||
{
|
||||
$this->reviewer = $reviewer;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getType(): ?ReviewType
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
public function setType(ReviewType $type): static
|
||||
{
|
||||
$this->type = $type;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, ReviewArea>
|
||||
*/
|
||||
public function getAreas(): Collection
|
||||
{
|
||||
return $this->areas;
|
||||
}
|
||||
|
||||
public function addArea(ReviewArea $area): static
|
||||
{
|
||||
if (!$this->areas->contains($area)) {
|
||||
$this->areas->add($area);
|
||||
$area->setReview($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeArea(ReviewArea $area): static
|
||||
{
|
||||
if ($this->areas->removeElement($area)) {
|
||||
// set the owning side to null (unless already changed)
|
||||
if ($area->getReview() === $this) {
|
||||
$area->setReview(null);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity\Project;
|
||||
|
||||
use App\Entity\DateCreatedTrait;
|
||||
use App\Entity\DateUpdatedTrait;
|
||||
use App\Mapping\Provider\EntityMapProvider;
|
||||
use App\Repository\Project\ReviewAreaRepository;
|
||||
use AutoMapper\Attribute\MapProvider;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[MapProvider(EntityMapProvider::class)]
|
||||
#[ORM\Entity(repositoryClass: ReviewAreaRepository::class)]
|
||||
class ReviewArea
|
||||
{
|
||||
use DateCreatedTrait;
|
||||
use DateUpdatedTrait;
|
||||
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\ManyToOne(inversedBy: 'areas')]
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
private ?Review $review = null;
|
||||
|
||||
#[ORM\Column(length: 255)]
|
||||
private ?string $title = null;
|
||||
|
||||
#[ORM\Column(type: Types::TEXT, nullable: true)]
|
||||
private ?string $summary = null;
|
||||
|
||||
#[ORM\Column(enumType: ReviewAreaRisk::class)]
|
||||
private ReviewAreaRisk $risk = ReviewAreaRisk::High;
|
||||
|
||||
/**
|
||||
* @var Collection<int, ReviewComment>
|
||||
*/
|
||||
#[ORM\OneToMany(targetEntity: ReviewComment::class, mappedBy: 'area')]
|
||||
private Collection $comments;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->comments = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getReview(): ?Review
|
||||
{
|
||||
return $this->review;
|
||||
}
|
||||
|
||||
public function setReview(?Review $review): static
|
||||
{
|
||||
$this->review = $review;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getTitle(): ?string
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
public function setTitle(string $title): static
|
||||
{
|
||||
$this->title = $title;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getSummary(): ?string
|
||||
{
|
||||
return $this->summary;
|
||||
}
|
||||
|
||||
public function setSummary(string $summary): static
|
||||
{
|
||||
$this->summary = $summary;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getRisk(): ?ReviewAreaRisk
|
||||
{
|
||||
return $this->risk;
|
||||
}
|
||||
|
||||
public function setRisk(?ReviewAreaRisk $risk): static
|
||||
{
|
||||
$this->risk = $risk;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, ReviewComment>
|
||||
*/
|
||||
public function getComments(): Collection
|
||||
{
|
||||
return $this->comments;
|
||||
}
|
||||
|
||||
public function addComment(ReviewComment $comment): static
|
||||
{
|
||||
if (!$this->comments->contains($comment)) {
|
||||
$this->comments->add($comment);
|
||||
$comment->setArea($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeComment(ReviewComment $comment): static
|
||||
{
|
||||
if ($this->comments->removeElement($comment)) {
|
||||
// set the owning side to null (unless already changed)
|
||||
if ($comment->getArea() === $this) {
|
||||
$comment->setArea(null);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity\Project;
|
||||
|
||||
enum ReviewAreaRisk: string
|
||||
{
|
||||
/**
|
||||
* The risk acceptable or none. Green light.
|
||||
*/
|
||||
case Low = 'low';
|
||||
|
||||
/**
|
||||
* The risk is moderate or somewhat worrying. Yellow light.
|
||||
*/
|
||||
case Mid = 'mid';
|
||||
|
||||
/**
|
||||
* The risk is severe and/or worrying. Red light.
|
||||
*/
|
||||
case High = 'high';
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity\Project;
|
||||
|
||||
use App\Entity\DateCreatedTrait;
|
||||
use App\Entity\DateUpdatedTrait;
|
||||
use App\Entity\User\User;
|
||||
use App\Mapping\Provider\EntityMapProvider;
|
||||
use App\Repository\Project\ReviewCommentRepository;
|
||||
use AutoMapper\Attribute\MapProvider;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[MapProvider(EntityMapProvider::class)]
|
||||
#[ORM\Entity(repositoryClass: ReviewCommentRepository::class)]
|
||||
class ReviewComment
|
||||
{
|
||||
use DateCreatedTrait;
|
||||
use DateUpdatedTrait;
|
||||
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\ManyToOne]
|
||||
private ?User $author = null;
|
||||
|
||||
#[ORM\Column(type: Types::TEXT)]
|
||||
private ?string $body = null;
|
||||
|
||||
#[ORM\ManyToOne(inversedBy: 'comments')]
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
private ?ReviewArea $area = null;
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getAuthor(): ?User
|
||||
{
|
||||
return $this->author;
|
||||
}
|
||||
|
||||
public function setAuthor(?User $author): static
|
||||
{
|
||||
$this->author = $author;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getBody(): ?string
|
||||
{
|
||||
return $this->body;
|
||||
}
|
||||
|
||||
public function setBody(string $body): static
|
||||
{
|
||||
$this->body = $body;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getArea(): ?ReviewArea
|
||||
{
|
||||
return $this->area;
|
||||
}
|
||||
|
||||
public function setArea(?ReviewArea $area): static
|
||||
{
|
||||
$this->area = $area;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity\Project;
|
||||
|
||||
enum ReviewType: string
|
||||
{
|
||||
/**
|
||||
* The goal is to determine if a Project is fit for campaigning or not.
|
||||
*/
|
||||
case Campaign = 'campaign';
|
||||
|
||||
/**
|
||||
* The goal is to determine if a Project is fit for payment or not.
|
||||
*/
|
||||
case Financial = 'financial';
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Entity\Project;
|
||||
|
||||
use App\Entity\Address;
|
||||
use App\Entity\Gateway\Charge;
|
||||
use App\Entity\User\User;
|
||||
use App\Entity\UserOwnedInterface;
|
||||
@@ -35,6 +36,12 @@ class RewardClaim implements UserOwnedInterface
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
private ?Charge $charge = null;
|
||||
|
||||
#[ORM\Column(enumType: RewardClaimStatus::class)]
|
||||
private ?RewardClaimStatus $status = RewardClaimStatus::InPending;
|
||||
|
||||
#[ORM\ManyToOne]
|
||||
private ?Address $address = null;
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
@@ -63,4 +70,28 @@ class RewardClaim implements UserOwnedInterface
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getStatus(): ?RewardClaimStatus
|
||||
{
|
||||
return $this->status;
|
||||
}
|
||||
|
||||
public function setStatus(RewardClaimStatus $status): static
|
||||
{
|
||||
$this->status = $status;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAddress(): ?Address
|
||||
{
|
||||
return $this->address;
|
||||
}
|
||||
|
||||
public function setAddress(?Address $address): static
|
||||
{
|
||||
$this->address = $address;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity\Project;
|
||||
|
||||
enum RewardClaimStatus: string
|
||||
{
|
||||
/**
|
||||
* The claim has not been yet processed by the reward provider.
|
||||
*/
|
||||
case InPending = 'in_pending';
|
||||
|
||||
/**
|
||||
* The claim has been fulfilled and the user can be considered as rewarded.
|
||||
*/
|
||||
case Fulfilled = 'fulfilled';
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\ThemeRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Gedmo\Mapping\Annotation as Gedmo;
|
||||
|
||||
#[ORM\Entity(repositoryClass: ThemeRepository::class)]
|
||||
class Theme
|
||||
{
|
||||
use LocalizedTrait;
|
||||
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\Column(length: 56, unique: true)]
|
||||
#[Gedmo\Slug(fields: ['name'])]
|
||||
private ?string $slug = null;
|
||||
|
||||
#[ORM\Column(length: 255)]
|
||||
#[Gedmo\Translatable()]
|
||||
private ?string $name = null;
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getSlug(): ?string
|
||||
{
|
||||
return $this->slug;
|
||||
}
|
||||
|
||||
public function setSlug(string $slug): static
|
||||
{
|
||||
$this->slug = $slug;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getName(): ?string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function setName(string $name): static
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ namespace App\Entity\User;
|
||||
|
||||
use App\Entity\Accounting\Accounting;
|
||||
use App\Entity\Accounting\AccountingOwnerInterface;
|
||||
use App\Entity\Address;
|
||||
use App\Entity\DateCreatedTrait;
|
||||
use App\Entity\DateUpdatedTrait;
|
||||
use App\Entity\DedupedTrait;
|
||||
@@ -124,6 +125,12 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface, Account
|
||||
#[ORM\Column(type: Types::TEXT, nullable: true)]
|
||||
private ?string $description = null;
|
||||
|
||||
/**
|
||||
* @var Collection<int, Address>
|
||||
*/
|
||||
#[ORM\OneToMany(targetEntity: Address::class, mappedBy: 'user')]
|
||||
private Collection $addresses;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->accounting = Accounting::of($this);
|
||||
@@ -133,6 +140,7 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface, Account
|
||||
|
||||
$this->emailConfirmed = false;
|
||||
$this->active = false;
|
||||
$this->addresses = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
@@ -393,4 +401,34 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface, Account
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, Address>
|
||||
*/
|
||||
public function getAddresses(): Collection
|
||||
{
|
||||
return $this->addresses;
|
||||
}
|
||||
|
||||
public function addAddress(Address $address): static
|
||||
{
|
||||
if (!$this->addresses->contains($address)) {
|
||||
$this->addresses->add($address);
|
||||
$address->setUser($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeAddress(Address $address): static
|
||||
{
|
||||
if ($this->addresses->removeElement($address)) {
|
||||
// set the owning side to null (unless already changed)
|
||||
if ($address->getUser() === $this) {
|
||||
$address->setUser(null);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\EventListener;
|
||||
|
||||
use App\Entity\Project\Project;
|
||||
use App\Entity\Project\ProjectStatus;
|
||||
use App\Service\Project\CalendarService;
|
||||
use Doctrine\Bundle\DoctrineBundle\Attribute\AsEntityListener;
|
||||
use Doctrine\ORM\Event\PreUpdateEventArgs;
|
||||
use Doctrine\ORM\Events;
|
||||
|
||||
#[AsEntityListener(
|
||||
event: Events::preUpdate,
|
||||
method: 'preUpdate',
|
||||
entity: Project::class
|
||||
)]
|
||||
final class ProjectsListener
|
||||
{
|
||||
public function __construct(
|
||||
private CalendarService $calendarService,
|
||||
) {}
|
||||
|
||||
public function preUpdate(
|
||||
Project $project,
|
||||
PreUpdateEventArgs $event,
|
||||
) {
|
||||
if (!$event->hasChangedField('status')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($project->getStatus() === ProjectStatus::InCampaign) {
|
||||
$calendar = $this->calendarService->makeCalendar($project->getDeadline());
|
||||
|
||||
$project->setCalendar($calendar);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,7 @@ final class OrderedLikeFilter extends AbstractFilter
|
||||
): void {
|
||||
if (
|
||||
!$this->isPropertyEnabled($property, $resourceClass)
|
||||
|| !$this->isPropertyMapped($property, $resourceClass)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
@@ -40,10 +41,18 @@ final class OrderedLikeFilter extends AbstractFilter
|
||||
$rootAlias = $queryBuilder->getRootAliases()[0];
|
||||
$likeParameter = $queryNameGenerator->generateParameterName($property);
|
||||
$instrParameter = $queryNameGenerator->generateParameterName(sprintf('%s%s', $property, 'instr'));
|
||||
$positionAlias = $queryNameGenerator->generateJoinAlias('position');
|
||||
|
||||
$queryBuilder
|
||||
->andWhere(sprintf('%s.%s LIKE :%s', $rootAlias, $property, $likeParameter))
|
||||
->addOrderBy(sprintf('INSTR(%s.%s, :%s)', $rootAlias, $property, $instrParameter), 'ASC')
|
||||
->addSelect(sprintf(
|
||||
'INSTR(%s.%s, :%s) AS HIDDEN %s',
|
||||
$rootAlias,
|
||||
$property,
|
||||
$instrParameter,
|
||||
$positionAlias
|
||||
))
|
||||
->addOrderBy($positionAlias, 'ASC')
|
||||
->addOrderBy(sprintf('%s.%s', $rootAlias, $property), 'ASC')
|
||||
->setParameter($likeParameter, sprintf('%%%s%%', $value))
|
||||
->setParameter($instrParameter, $value)
|
||||
|
||||
+18
-3
@@ -16,15 +16,30 @@ class Link
|
||||
*/
|
||||
public ?string $rel;
|
||||
|
||||
public static function tryFrom($value): self
|
||||
/**
|
||||
* The known organization behind this link.
|
||||
*/
|
||||
public function getOrg(): LinkOrg
|
||||
{
|
||||
return LinkOrg::fromUrl($this->url);
|
||||
}
|
||||
|
||||
public static function tryFrom(mixed $value): self
|
||||
{
|
||||
if ($value instanceof self) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if (\is_array($value)) {
|
||||
$link = new self();
|
||||
$link->url = $value['url'];
|
||||
$link->rel = $value['rel'];
|
||||
|
||||
return $link;
|
||||
}
|
||||
|
||||
$link = new self();
|
||||
$link->url = $value['url'];
|
||||
$link->rel = $value['rel'];
|
||||
$link->url = $value;
|
||||
|
||||
return $link;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Library;
|
||||
|
||||
enum LinkOrg: string
|
||||
{
|
||||
case Instagram = 'instagram';
|
||||
case Facebook = 'facebook';
|
||||
case GitHub = 'github';
|
||||
case LinkedIn = 'linkedin';
|
||||
case Identica = 'identica';
|
||||
case Unknown = 'unknown';
|
||||
|
||||
private static function domains(): array
|
||||
{
|
||||
return [
|
||||
'instagram.com' => self::Instagram,
|
||||
'facebook.com' => self::Facebook,
|
||||
'fb.com' => self::Facebook,
|
||||
'github.com' => self::GitHub,
|
||||
'linkedin.com' => self::LinkedIn,
|
||||
'identi.ca' => self::Identica,
|
||||
];
|
||||
}
|
||||
|
||||
public static function fromUrl(string $url): self
|
||||
{
|
||||
$host = \parse_url($url, PHP_URL_HOST) ?? '';
|
||||
$host = \join('.', \array_slice(\explode('.', $host), -2));
|
||||
|
||||
foreach (self::domains() as $domain => $network) {
|
||||
if ($host === $domain || \str_ends_with($host, '.'.$domain)) {
|
||||
return $network;
|
||||
}
|
||||
}
|
||||
|
||||
return self::Unknown;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace App\Mapping\Transformer;
|
||||
|
||||
use App\Library\Link;
|
||||
use App\Service\Scout\ScoutResult;
|
||||
use App\Service\Scout\ScoutService;
|
||||
use AutoMapper\Transformer\PropertyTransformer\PropertyTransformerInterface;
|
||||
|
||||
class RawLinksMapTransformer implements PropertyTransformerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private ScoutService $scoutService,
|
||||
) {}
|
||||
|
||||
public function transform(mixed $value, object|array $source, array $context): mixed
|
||||
{
|
||||
$links = [];
|
||||
foreach ($value as $rawLink) {
|
||||
$info = $this->scoutService->get($rawLink);
|
||||
|
||||
$link = new Link();
|
||||
$link->url = $info->getUri();
|
||||
$link->rel = $this->getRel($info);
|
||||
|
||||
$links[] = $link;
|
||||
}
|
||||
|
||||
return $links;
|
||||
}
|
||||
|
||||
private function getRel(ScoutResult $info): ?string
|
||||
{
|
||||
$nodes = (new \DOMXPath($info->getDocument()->getDocument()))->query('//a[@rel]');
|
||||
|
||||
if ($nodes->length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$anchor = $nodes->item(0);
|
||||
if (!$anchor instanceof \DOMElement) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $anchor->getAttribute('rel');
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\OAuth;
|
||||
namespace App\OAuth2\Client;
|
||||
|
||||
use App\Service\UserService;
|
||||
use League\OAuth2\Client\Provider\AbstractProvider;
|
||||
@@ -12,19 +12,19 @@ use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* Provides supports for OAuth Authorization Code flows with Decidim instances.
|
||||
* Provides supports for OAuth2 Authorization Code flows with Decidim instances.
|
||||
*
|
||||
* HOW-TO USE
|
||||
* 1. Add a .png image in `public/oauth/providers` dir to show the provider's logo in the login page
|
||||
* 1. Add a .png image in `public/oauth2/providers` dir to show the provider's logo in the login page
|
||||
* 2. Add a new provider under `clients` key in `config/packages/knpu_oauth2_client.yaml`:
|
||||
* ```yaml
|
||||
* decidim_<instance name>:
|
||||
* type: generic
|
||||
* provider_class: App\OAuth\DecidimProvider
|
||||
* provider_class: App\OAuth2\Client\DecidimProvider
|
||||
* provider_options:
|
||||
* url: <instance web address>
|
||||
* client_id: "%env(<instance oauth client id>)%"
|
||||
* client_secret: "%env(<instance oauth client secret>)%"
|
||||
* client_id: "%env(<instance's OAuth2 Client id>)%"
|
||||
* client_secret: "%env(<instance's OAuth2 Client secret>)%"
|
||||
* redirect_route: oauth_providers_callback
|
||||
* redirect_params:
|
||||
* provider: decidim_<instance name>
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\OAuth;
|
||||
namespace App\OAuth2\Client;
|
||||
|
||||
use League\OAuth2\Client\Provider\ResourceOwnerInterface;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\OAuth;
|
||||
namespace App\OAuth2\Client;
|
||||
|
||||
interface TokenOwnerInterface
|
||||
{
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\OAuth2\Server;
|
||||
|
||||
use App\Entity\User\User;
|
||||
use League\Bundle\OAuth2ServerBundle\Converter\UserConverterInterface;
|
||||
use League\Bundle\OAuth2ServerBundle\Entity\User as OAuth2User;
|
||||
use League\OAuth2\Server\Entities\UserEntityInterface;
|
||||
use Symfony\Component\DependencyInjection\Attribute\AsDecorator;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
|
||||
#[AsDecorator(UserConverterInterface::class)]
|
||||
final class UserConverter implements UserConverterInterface
|
||||
{
|
||||
public function toLeague(UserInterface $user): UserEntityInterface
|
||||
{
|
||||
if (!$user instanceof User) {
|
||||
throw new \LogicException(sprintf(
|
||||
'Expected %s, got %s.',
|
||||
User::class,
|
||||
$user::class,
|
||||
));
|
||||
}
|
||||
|
||||
$userEntity = new OAuth2User();
|
||||
$userEntity->setIdentifier((string) $user->getId());
|
||||
|
||||
return $userEntity;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,7 @@
|
||||
# Introduction
|
||||
DTF's Goteo v4 API instance by [Platoniq](https://platoniq.net/).
|
||||
|
||||
[Co-op Cloud Git](https://git.coopcloud.tech/DemocraticTech.Fund/crowd-fund)
|
||||
|
||||
The v4 API is a multi-capable web API based on the [API Platform](https://api-platform.com/) framework, built to support the future generation of the [Goteo](https://goteo.org) crowdfunding platform, aiming to be it's new underlying engine.
|
||||
|
||||
|
||||
@@ -31,6 +31,9 @@ trait RedocExtensionsTrait
|
||||
'name' => 'Projects',
|
||||
'tags' => [
|
||||
'Project',
|
||||
'ProjectReview',
|
||||
'ProjectReviewArea',
|
||||
'ProjectReviewComment',
|
||||
'ProjectReward',
|
||||
'ProjectRewardClaim',
|
||||
'ProjectBudgetItem',
|
||||
@@ -38,7 +41,13 @@ trait RedocExtensionsTrait
|
||||
'ProjectSupport',
|
||||
'ProjectCollaboration',
|
||||
'ProjectCollaborationCandidacy',
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Categorization',
|
||||
'tags' => [
|
||||
'Category',
|
||||
'Theme',
|
||||
],
|
||||
],
|
||||
[
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\Address;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<Address>
|
||||
*/
|
||||
class AddressRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, Address::class);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @return Address[] Returns an array of Address objects
|
||||
// */
|
||||
// public function findByExampleField($value): array
|
||||
// {
|
||||
// return $this->createQueryBuilder('a')
|
||||
// ->andWhere('a.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->orderBy('a.id', 'ASC')
|
||||
// ->setMaxResults(10)
|
||||
// ->getQuery()
|
||||
// ->getResult()
|
||||
// ;
|
||||
// }
|
||||
|
||||
// public function findOneBySomeField($value): ?Address
|
||||
// {
|
||||
// return $this->createQueryBuilder('a')
|
||||
// ->andWhere('a.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->getQuery()
|
||||
// ->getOneOrNullResult()
|
||||
// ;
|
||||
// }
|
||||
}
|
||||
@@ -11,6 +11,8 @@ use Doctrine\Persistence\ManagerRegistry;
|
||||
*/
|
||||
class CategoryRepository extends ServiceEntityRepository
|
||||
{
|
||||
use FindByIdOrSlugTrait;
|
||||
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, Category::class);
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
|
||||
/**
|
||||
* @template T of object
|
||||
*
|
||||
* @mixin ServiceEntityRepository
|
||||
*/
|
||||
trait FindByIdOrSlugTrait
|
||||
{
|
||||
/**
|
||||
* @return T|null
|
||||
*/
|
||||
public function findOneByIdOrSlug(mixed $idOrSlug): ?object
|
||||
{
|
||||
if (\ctype_digit((string) $idOrSlug)) {
|
||||
return $this->find($idOrSlug);
|
||||
}
|
||||
|
||||
return $this->findOneBy(['slug' => $idOrSlug]);
|
||||
}
|
||||
|
||||
public function getByIdOrSlugQuery(mixed $idOrSlug): QueryBuilder
|
||||
{
|
||||
/** @var QueryBuilder */
|
||||
$queryBuilder = $this->createQueryBuilder('o');
|
||||
$queryBuilder->where(\ctype_digit((string) $idOrSlug) ? 'o.id = :idOrSlugField' : 'o.slug = :idOrSlugField');
|
||||
$queryBuilder->setParameter('idOrSlugField', $idOrSlug);
|
||||
|
||||
return $queryBuilder;
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ namespace App\Repository\Project;
|
||||
|
||||
use App\Entity\Accounting\Accounting;
|
||||
use App\Entity\Project\Project;
|
||||
use App\Repository\FindByIdOrSlugTrait;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
@@ -17,6 +18,8 @@ use Doctrine\Persistence\ManagerRegistry;
|
||||
*/
|
||||
class ProjectRepository extends ServiceEntityRepository
|
||||
{
|
||||
use FindByIdOrSlugTrait;
|
||||
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, Project::class);
|
||||
@@ -31,15 +34,6 @@ class ProjectRepository extends ServiceEntityRepository
|
||||
->getOneOrNullResult();
|
||||
}
|
||||
|
||||
public function findOneByIdOrSlug(mixed $idOrSlug): ?Project
|
||||
{
|
||||
if (\is_numeric($idOrSlug)) {
|
||||
return $this->find($idOrSlug);
|
||||
}
|
||||
|
||||
return $this->findOneBy(['slug' => $idOrSlug]);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @return Project[] Returns an array of Project objects
|
||||
// */
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repository\Project;
|
||||
|
||||
use App\Entity\Project\ReviewArea;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<ReviewArea>
|
||||
*/
|
||||
class ReviewAreaRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, ReviewArea::class);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @return ReviewArea[] Returns an array of ReviewArea objects
|
||||
// */
|
||||
// public function findByExampleField($value): array
|
||||
// {
|
||||
// return $this->createQueryBuilder('r')
|
||||
// ->andWhere('r.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->orderBy('r.id', 'ASC')
|
||||
// ->setMaxResults(10)
|
||||
// ->getQuery()
|
||||
// ->getResult()
|
||||
// ;
|
||||
// }
|
||||
|
||||
// public function findOneBySomeField($value): ?ReviewArea
|
||||
// {
|
||||
// return $this->createQueryBuilder('r')
|
||||
// ->andWhere('r.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->getQuery()
|
||||
// ->getOneOrNullResult()
|
||||
// ;
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repository\Project;
|
||||
|
||||
use App\Entity\Project\ReviewComment;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<ReviewComment>
|
||||
*/
|
||||
class ReviewCommentRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, ReviewComment::class);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @return ReviewComment[] Returns an array of ReviewComment objects
|
||||
// */
|
||||
// public function findByExampleField($value): array
|
||||
// {
|
||||
// return $this->createQueryBuilder('r')
|
||||
// ->andWhere('r.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->orderBy('r.id', 'ASC')
|
||||
// ->setMaxResults(10)
|
||||
// ->getQuery()
|
||||
// ->getResult()
|
||||
// ;
|
||||
// }
|
||||
|
||||
// public function findOneBySomeField($value): ?ReviewComment
|
||||
// {
|
||||
// return $this->createQueryBuilder('r')
|
||||
// ->andWhere('r.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->getQuery()
|
||||
// ->getOneOrNullResult()
|
||||
// ;
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repository\Project;
|
||||
|
||||
use App\Entity\Project\Review;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<Review>
|
||||
*/
|
||||
class ReviewRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, Review::class);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @return Review[] Returns an array of Review objects
|
||||
// */
|
||||
// public function findByExampleField($value): array
|
||||
// {
|
||||
// return $this->createQueryBuilder('r')
|
||||
// ->andWhere('r.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->orderBy('r.id', 'ASC')
|
||||
// ->setMaxResults(10)
|
||||
// ->getQuery()
|
||||
// ->getResult()
|
||||
// ;
|
||||
// }
|
||||
|
||||
// public function findOneBySomeField($value): ?Review
|
||||
// {
|
||||
// return $this->createQueryBuilder('r')
|
||||
// ->andWhere('r.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->getQuery()
|
||||
// ->getOneOrNullResult()
|
||||
// ;
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\Theme;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<Theme>
|
||||
*/
|
||||
class ThemeRepository extends ServiceEntityRepository
|
||||
{
|
||||
use FindByIdOrSlugTrait;
|
||||
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, Theme::class);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @return Theme[] Returns an array of Theme objects
|
||||
// */
|
||||
// public function findByExampleField($value): array
|
||||
// {
|
||||
// return $this->createQueryBuilder('t')
|
||||
// ->andWhere('t.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->orderBy('t.id', 'ASC')
|
||||
// ->setMaxResults(10)
|
||||
// ->getQuery()
|
||||
// ->getResult()
|
||||
// ;
|
||||
// }
|
||||
|
||||
// public function findOneBySomeField($value): ?Theme
|
||||
// {
|
||||
// return $this->createQueryBuilder('t')
|
||||
// ->andWhere('t.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->getQuery()
|
||||
// ->getOneOrNullResult()
|
||||
// ;
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace App\Security\Voter;
|
||||
|
||||
use App\ApiResource\Project\ReviewAreaApiResource;
|
||||
use App\Entity\Project\ReviewArea;
|
||||
use App\Entity\User\User;
|
||||
use App\Mapping\AutoMapper;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
|
||||
|
||||
final class ReviewAreaVoter extends Voter
|
||||
{
|
||||
use UserOwnedVoterTrait;
|
||||
|
||||
public const EDIT = 'REVIEWAREA_EDIT';
|
||||
public const VIEW = 'REVIEWAREA_VIEW';
|
||||
|
||||
public function __construct(
|
||||
private AutoMapper $mapper,
|
||||
) {}
|
||||
|
||||
protected function supports(string $attribute, mixed $subject): bool
|
||||
{
|
||||
return in_array($attribute, [self::EDIT, self::VIEW])
|
||||
&& $subject instanceof ReviewAreaApiResource;
|
||||
}
|
||||
|
||||
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
|
||||
{
|
||||
$user = $token->getUser();
|
||||
if (!$user instanceof User) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($user->hasRoles(['ROLE_ADMIN'])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @var ReviewArea */
|
||||
$area = $this->mapper->map($subject, ReviewArea::class);
|
||||
if (!$area || !$area instanceof ReviewArea) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$project = $area->getReview()->getProject();
|
||||
|
||||
switch ($attribute) {
|
||||
case self::EDIT:
|
||||
return $area->getReview()->isReviewedBy($user);
|
||||
case self::VIEW:
|
||||
return $this->isOwnerOf($project, $user);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace App\Security\Voter;
|
||||
|
||||
use App\ApiResource\Project\RewardClaimApiResource;
|
||||
use App\Entity\User\User;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
|
||||
final class RewardClaimVoter extends Voter
|
||||
{
|
||||
use UserOwnedVoterTrait;
|
||||
|
||||
public const EDIT = 'CLAIM_EDIT';
|
||||
public const VIEW = 'CLAIM_VIEW';
|
||||
public const OWNS = 'CLAIM_OWNS';
|
||||
|
||||
protected function supports(string $attribute, mixed $subject): bool
|
||||
{
|
||||
return in_array($attribute, [self::EDIT, self::VIEW, self::OWNS])
|
||||
&& $subject instanceof RewardClaimApiResource;
|
||||
}
|
||||
|
||||
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
|
||||
{
|
||||
switch ($attribute) {
|
||||
case self::VIEW:
|
||||
return true;
|
||||
case self::EDIT:
|
||||
return $this->voteEdit($subject, $token->getUser());
|
||||
case self::OWNS:
|
||||
return $this->voteOwns($subject, $token->getUser());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function voteEdit(RewardClaimApiResource $claim, ?UserInterface $user): bool
|
||||
{
|
||||
if (!$user instanceof User) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($user->hasRoles(['ROLE_ADMIN'])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->isOwnerOf($claim->reward->project, $user);
|
||||
}
|
||||
|
||||
private function voteOwns(RewardClaimApiResource $claim, ?UserInterface $user): bool
|
||||
{
|
||||
if (!$user instanceof User) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($user->hasRoles(['ROLE_ADMIN'])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->isOwnerOf($claim->reward, $user);
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,6 @@ class UserOwnedVoter extends Voter
|
||||
*/
|
||||
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
|
||||
{
|
||||
return $this->isOwnerOf($subject, $token->getUser());
|
||||
return $subject !== null && $this->isOwnerOf($subject, $token->getUser());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Security\Voter;
|
||||
|
||||
use App\ApiResource\Accounting\AccountingApiResource;
|
||||
use App\ApiResource\Project\ProjectApiResource;
|
||||
use App\ApiResource\User\UserApiResource;
|
||||
use App\Entity\User\User;
|
||||
use App\Entity\UserOwnedInterface;
|
||||
@@ -31,6 +32,10 @@ trait UserOwnedVoterTrait
|
||||
return $subject->id === $user->getAccounting()->getId();
|
||||
}
|
||||
|
||||
if ($subject instanceof ProjectApiResource) {
|
||||
return $subject->owner->id === $user->getId();
|
||||
}
|
||||
|
||||
if ($subject instanceof UserOwnedInterface) {
|
||||
return $subject->isOwnedBy($user);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Service\Project;
|
||||
|
||||
use App\Entity\Project\Project;
|
||||
use App\Entity\Project\ProjectStatus;
|
||||
use App\Entity\Project\ReviewType;
|
||||
use App\Entity\User\User;
|
||||
use App\Security\Voter\UserOwnedVoterTrait;
|
||||
|
||||
@@ -19,6 +20,11 @@ class ProjectService
|
||||
ProjectStatus::ToFunding->value => [ProjectStatus::InFunding],
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
private ReviewService $reviewService,
|
||||
private CalendarService $calendarService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Check if an User can move a Project to some status.
|
||||
*
|
||||
@@ -47,4 +53,34 @@ class ProjectService
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves a Project to a given ProjectStatus.
|
||||
*
|
||||
* @param ProjectStatus $to The new status to where the Project will move
|
||||
*
|
||||
* @return Project The resulting Project
|
||||
*/
|
||||
public function transition(Project $project, ProjectStatus $to): Project
|
||||
{
|
||||
$from = $project->getStatus();
|
||||
|
||||
if ($from === $to) {
|
||||
return $project;
|
||||
}
|
||||
|
||||
$project->setStatus($to);
|
||||
|
||||
return match ([$from, $to]) {
|
||||
[ProjectStatus::InDraft, ProjectStatus::ToCampaignReview] => $project->addReview(
|
||||
$this->reviewService->makeReview(ReviewType::Campaign)
|
||||
),
|
||||
|
||||
[ProjectStatus::ToCampaign, ProjectStatus::InCampaign] => $project->setCalendar(
|
||||
$this->calendarService->makeCalendar($project->getDeadline())
|
||||
),
|
||||
|
||||
default => $project,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace App\Service\Project;
|
||||
|
||||
use App\Entity\Project\Review;
|
||||
use App\Entity\Project\ReviewArea;
|
||||
use App\Entity\Project\ReviewType;
|
||||
|
||||
class ReviewService
|
||||
{
|
||||
public function makeReview(ReviewType $type): Review
|
||||
{
|
||||
$review = new Review();
|
||||
$review->setType($type);
|
||||
|
||||
/** @var ReviewArea[] */
|
||||
$areas = match ($type) {
|
||||
ReviewType::Campaign => $this->getCampaignReviewAreas(),
|
||||
ReviewType::Financial => $this->getFinancialReviewAreas(),
|
||||
};
|
||||
|
||||
foreach ($areas as $area) {
|
||||
$review->addArea($area);
|
||||
}
|
||||
|
||||
return $review;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ReviewArea[]
|
||||
*/
|
||||
private function getCampaignReviewAreas(): array
|
||||
{
|
||||
$config = new ReviewArea();
|
||||
$config->setTitle('configuration');
|
||||
|
||||
$info = new ReviewArea();
|
||||
$info->setTitle('info');
|
||||
|
||||
$rewards = new ReviewArea();
|
||||
$rewards->setTitle('rewards');
|
||||
|
||||
$collabs = new ReviewArea();
|
||||
$collabs->setTitle('collaborations');
|
||||
|
||||
$budget = new ReviewArea();
|
||||
$budget->setTitle('budget');
|
||||
|
||||
$about = new ReviewArea();
|
||||
$about->setTitle('about');
|
||||
|
||||
return [
|
||||
$config,
|
||||
$info,
|
||||
$rewards,
|
||||
$collabs,
|
||||
$budget,
|
||||
$about,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ReviewArea[]
|
||||
*
|
||||
* @todo Add financial review areas
|
||||
*/
|
||||
private function getFinancialReviewAreas(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
+2
-5
@@ -4,13 +4,13 @@ namespace App\Service\Scout;
|
||||
|
||||
use Psr\Http\Message\UriInterface;
|
||||
|
||||
class FileUriException extends \Exception
|
||||
class NonCrawlableUriException extends \Exception
|
||||
{
|
||||
private UriInterface $uri;
|
||||
|
||||
public function __construct(
|
||||
UriInterface $uri,
|
||||
string $message = 'Cannot scout URLs to files (%s)',
|
||||
string $message = 'Cannot scout %s: it is not a Web page',
|
||||
int $code = 0,
|
||||
?\Throwable $previous = null,
|
||||
) {
|
||||
@@ -19,9 +19,6 @@ class FileUriException extends \Exception
|
||||
$this->uri = $uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* The URI to the file.
|
||||
*/
|
||||
public function getUri(): UriInterface
|
||||
{
|
||||
return $this->uri;
|
||||
@@ -5,6 +5,7 @@ namespace App\Service\Scout;
|
||||
use Embed\Embed;
|
||||
use Embed\Http\Crawler;
|
||||
use Psr\Http\Client\ClientInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Symfony\Component\Validator\Constraints\NotBlank;
|
||||
use Symfony\Component\Validator\Constraints\Url;
|
||||
use Symfony\Component\Validator\Validation;
|
||||
@@ -39,8 +40,8 @@ class ScoutService
|
||||
/**
|
||||
* @param string $url A URL to an external resource
|
||||
*
|
||||
* @throws InvalidUriException When the given $url string could not be validated as an actual URL
|
||||
* @throws FileUriException If the given $url string points to a file
|
||||
* @throws InvalidUriException When the given $url string could not be validated as an actual URL
|
||||
* @throws NonCrawlableUriException If the given $url string points to a file
|
||||
*/
|
||||
public function get(string $url): ScoutResult
|
||||
{
|
||||
@@ -54,9 +55,12 @@ class ScoutService
|
||||
}
|
||||
|
||||
$uri = $this->embed->getCrawler()->createUri($url);
|
||||
$response = $this->httpClient->sendRequest(
|
||||
new \GuzzleHttp\Psr7\Request('HEAD', $url)
|
||||
);
|
||||
|
||||
if (\pathinfo($uri->getPath(), \PATHINFO_EXTENSION)) {
|
||||
throw new FileUriException($uri);
|
||||
if (!$this->isCrawlable($response)) {
|
||||
throw new NonCrawlableUriException($uri);
|
||||
}
|
||||
|
||||
$info = $this->embed->get($url);
|
||||
@@ -82,4 +86,20 @@ class ScoutService
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function isCrawlable(ResponseInterface $response): bool
|
||||
{
|
||||
$contentType = $response->getHeaderLine('Content-Type');
|
||||
|
||||
if ($contentType === '') {
|
||||
return true;
|
||||
}
|
||||
|
||||
$contentType = \strtolower(\trim(\explode(';', $contentType)[0]));
|
||||
|
||||
return \in_array($contentType, [
|
||||
'text/html',
|
||||
'application/xhtml+xml',
|
||||
], true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,20 +20,14 @@ class CategoryStateProvider implements ProviderInterface
|
||||
|
||||
public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
|
||||
{
|
||||
$idOrSlug = $uriVariables['idOrSlug'];
|
||||
|
||||
$queryBuilder = $this->categoryRepository->createQueryBuilder('c');
|
||||
$queryBuilder->where(\is_numeric($idOrSlug) ? 'c.id = :value' : 'c.slug = :value');
|
||||
$queryBuilder->setParameter('value', $idOrSlug);
|
||||
|
||||
$queryBuilder = $this->categoryRepository->getByIdOrSlugQuery($uriVariables['idOrSlug']);
|
||||
$query = $this->addLocalizationHints($queryBuilder, $this->getAcceptedLanguages($context));
|
||||
$category = $query->getOneOrNullResult();
|
||||
|
||||
$project = $query->getOneOrNullResult();
|
||||
|
||||
if ($project === null) {
|
||||
if ($category === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->autoMapper->map($project, CategoryApiResource::class);
|
||||
return $this->autoMapper->map($category, CategoryApiResource::class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@ use App\ApiResource\Project\ProjectApiResource;
|
||||
use App\Dto\ProjectCreationDto;
|
||||
use App\Dto\ProjectUpdationDto;
|
||||
use App\Entity\Project\Project;
|
||||
use App\Entity\Project\ProjectCalendar;
|
||||
use App\Mapping\AutoMapper;
|
||||
use App\Service\Project\CalendarService;
|
||||
use App\Service\Project\ProjectService;
|
||||
use App\State\EntityStateProcessor;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
@@ -24,6 +24,7 @@ class ProjectStateProcessor implements ProcessorInterface
|
||||
private AutoMapper $autoMapper,
|
||||
private Security $security,
|
||||
private ProjectService $projectService,
|
||||
private CalendarService $calendarService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -33,13 +34,11 @@ class ProjectStateProcessor implements ProcessorInterface
|
||||
*/
|
||||
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = [])
|
||||
{
|
||||
if ($data instanceof ProjectCreationDto) {
|
||||
$project = $this->getProjectFromCreation($data);
|
||||
} elseif ($data instanceof ProjectUpdationDto) {
|
||||
$project = $this->getProjectFromUpdate($data, $context);
|
||||
} else {
|
||||
throw new \InvalidArgumentException('Unsupported input for Project resource');
|
||||
}
|
||||
$project = match ($data::class) {
|
||||
ProjectCreationDto::class => $this->getProjectFromCreation($data),
|
||||
ProjectUpdationDto::class => $this->getProjectFromUpdate($data, $context),
|
||||
default => throw new \InvalidArgumentException('Unsupported input for Project resource'),
|
||||
};
|
||||
|
||||
$project = $this->entityStateProcessor->process($project, $operation, $uriVariables, $context);
|
||||
|
||||
@@ -60,7 +59,6 @@ class ProjectStateProcessor implements ProcessorInterface
|
||||
$project = $this->autoMapper->map($data, Project::class);
|
||||
|
||||
$owner = $this->security->getUser();
|
||||
|
||||
if (!$owner) {
|
||||
throw new AuthenticationException();
|
||||
}
|
||||
@@ -68,28 +66,25 @@ class ProjectStateProcessor implements ProcessorInterface
|
||||
$project->setOwner($owner);
|
||||
|
||||
if (!isset($data->calendar->release)) {
|
||||
$data->calendar->release = new \DateTimeImmutable('+28 days');
|
||||
$project->setCalendar($this->calendarService->makeCalendar(
|
||||
$project->getDeadline(),
|
||||
new \DateTimeImmutable('+28 days')
|
||||
));
|
||||
}
|
||||
|
||||
$calendar = new ProjectCalendar();
|
||||
$calendar->release = $data->calendar->release;
|
||||
|
||||
$project->setCalendar($calendar);
|
||||
|
||||
return $project;
|
||||
}
|
||||
|
||||
private function getProjectFromUpdate(ProjectUpdationDto $data, array $context): Project
|
||||
{
|
||||
/** @var Project */
|
||||
$project = $this->autoMapper->map($context['previous_data'], Project::class);
|
||||
|
||||
$actor = $this->security->getUser();
|
||||
|
||||
if (!$actor) {
|
||||
throw new AuthenticationException();
|
||||
}
|
||||
|
||||
/** @var Project */
|
||||
$project = $this->autoMapper->map($context['previous_data'], Project::class);
|
||||
|
||||
if (isset($data->status) && $data->status !== $project->getStatus()) {
|
||||
if (!$this->projectService->canTransition($actor, $project, $data->status)) {
|
||||
throw new AccessDeniedException(\sprintf(
|
||||
@@ -98,6 +93,8 @@ class ProjectStateProcessor implements ProcessorInterface
|
||||
$data->status->value
|
||||
));
|
||||
}
|
||||
|
||||
$project = $this->projectService->transition($project, $data->status);
|
||||
}
|
||||
|
||||
return $this->autoMapper->map($data, $project);
|
||||
|
||||
@@ -20,14 +20,8 @@ class ProjectStateProvider implements ProviderInterface
|
||||
|
||||
public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
|
||||
{
|
||||
$idOrSlug = $uriVariables['idOrSlug'];
|
||||
|
||||
$queryBuilder = $this->projectRepository->createQueryBuilder('p');
|
||||
$queryBuilder->where(\is_numeric($idOrSlug) ? 'p.id = :value' : 'p.slug = :value');
|
||||
$queryBuilder->setParameter('value', $idOrSlug);
|
||||
|
||||
$queryBuilder = $this->projectRepository->getByIdOrSlugQuery($uriVariables['idOrSlug']);
|
||||
$query = $this->addLocalizationHints($queryBuilder, $this->getAcceptedLanguages($context));
|
||||
|
||||
$project = $query->getOneOrNullResult();
|
||||
|
||||
if ($project === null) {
|
||||
|
||||
@@ -7,44 +7,36 @@ use ApiPlatform\Metadata\Operation;
|
||||
use ApiPlatform\State\ProcessorInterface;
|
||||
use App\ApiResource\Project\RewardClaimApiResource;
|
||||
use App\Dto\RewardClaimCreationDto;
|
||||
use App\Dto\RewardClaimUpdationDto;
|
||||
use App\Entity\Project\RewardClaim;
|
||||
use App\Mapping\AutoMapper;
|
||||
use App\Repository\Project\RewardClaimRepository;
|
||||
use App\Security\Voter\AccountingVoter;
|
||||
use App\State\EntityStateProcessor;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||
|
||||
class RewardClaimStateProcessor implements ProcessorInterface
|
||||
{
|
||||
public function __construct(
|
||||
private EntityStateProcessor $entityStateProcessor,
|
||||
private AutoMapper $autoMapper,
|
||||
private RewardClaimRepository $rewardClaimRepository,
|
||||
private Security $security,
|
||||
private EntityStateProcessor $entityStateProcessor,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @param RewardClaimApiResource|RewardClaimCreationDto $data
|
||||
* @param RewardClaimCreationDto|RewardClaimUpdationDto $data
|
||||
*
|
||||
* @return RewardClaimApiResource|null
|
||||
*/
|
||||
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = [])
|
||||
{
|
||||
/** @var RewardClaim */
|
||||
$claim = $this->autoMapper->map($data, RewardClaim::class);
|
||||
|
||||
if ($data instanceof RewardClaimCreationDto) {
|
||||
$owner = $this->security->getUser();
|
||||
|
||||
if (!$owner) {
|
||||
throw new AuthenticationException();
|
||||
}
|
||||
|
||||
$claim->setOwner($owner);
|
||||
|
||||
$reward = $claim->getReward();
|
||||
$reward->addClaim($claim);
|
||||
|
||||
$claim->setReward($reward);
|
||||
}
|
||||
$claim = match ($data::class) {
|
||||
RewardClaimUpdationDto::class => $this->getClaimFromUpdate($context),
|
||||
RewardClaimCreationDto::class => $this->getClaimFromCreate($data),
|
||||
};
|
||||
|
||||
$claim = $this->entityStateProcessor->process($claim, $operation, $uriVariables, $context);
|
||||
|
||||
@@ -52,6 +44,37 @@ class RewardClaimStateProcessor implements ProcessorInterface
|
||||
return;
|
||||
}
|
||||
|
||||
if ($claim === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->autoMapper->map($claim, $data);
|
||||
}
|
||||
|
||||
private function getClaimFromUpdate(array $context): RewardClaim
|
||||
{
|
||||
$claim = $this->rewardClaimRepository->find($context['previous_data']->id);
|
||||
if (!$claim) {
|
||||
throw new NotFoundHttpException();
|
||||
}
|
||||
|
||||
return $claim;
|
||||
}
|
||||
|
||||
private function getClaimFromCreate(RewardClaimCreationDto $data): RewardClaim
|
||||
{
|
||||
/** @var RewardClaim */
|
||||
$claim = $this->autoMapper->map($data, RewardClaim::class);
|
||||
|
||||
$origin = $claim->getCharge()->getCheckout()->getOrigin();
|
||||
if (!$this->security->isGranted(AccountingVoter::EDIT, $origin)) {
|
||||
throw new AuthenticationException();
|
||||
}
|
||||
|
||||
$owner = $origin->getUser();
|
||||
|
||||
$claim->setOwner($owner);
|
||||
|
||||
return $claim;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\State;
|
||||
|
||||
use ApiPlatform\Metadata\Operation;
|
||||
use ApiPlatform\State\ProviderInterface;
|
||||
use App\ApiResource\ThemeApiResource;
|
||||
use App\Doctrine\LocalizedExtensionTrait;
|
||||
use App\Mapping\AutoMapper;
|
||||
use App\Repository\ThemeRepository;
|
||||
|
||||
class ThemeStateProvider implements ProviderInterface
|
||||
{
|
||||
use LocalizedExtensionTrait;
|
||||
|
||||
public function __construct(
|
||||
private ThemeRepository $themeRepository,
|
||||
private AutoMapper $autoMapper,
|
||||
) {}
|
||||
|
||||
public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
|
||||
{
|
||||
$queryBuilder = $this->themeRepository->getByIdOrSlugQuery($uriVariables['idOrSlug']);
|
||||
$query = $this->addLocalizationHints($queryBuilder, $this->getAcceptedLanguages($context));
|
||||
$theme = $query->getOneOrNullResult();
|
||||
|
||||
if ($theme === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->autoMapper->map($theme, ThemeApiResource::class);
|
||||
}
|
||||
}
|
||||
@@ -2,44 +2,58 @@
|
||||
|
||||
namespace App\State\User;
|
||||
|
||||
use ApiPlatform\Doctrine\Common\State\PersistProcessor;
|
||||
use ApiPlatform\Metadata as API;
|
||||
use ApiPlatform\Metadata\DeleteOperationInterface;
|
||||
use ApiPlatform\State\ProcessorInterface;
|
||||
use App\ApiResource\User\UserApiResource;
|
||||
use App\Dto\User\UserUpdationDto;
|
||||
use App\Entity\User\Organization;
|
||||
use App\Entity\User\User;
|
||||
use App\Entity\User\UserType;
|
||||
use App\Mapping\AutoMapper;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
use App\Repository\User\UserRepository;
|
||||
use App\State\EntityStateProcessor;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||
|
||||
class UserStateProcessor implements ProcessorInterface
|
||||
{
|
||||
public function __construct(
|
||||
private AutoMapper $autoMapper,
|
||||
#[Autowire(service: PersistProcessor::class)]
|
||||
private ProcessorInterface $persistProcessor,
|
||||
private UserRepository $userRepository,
|
||||
private UserPasswordHasherInterface $userPasswordHasher,
|
||||
private EntityStateProcessor $entityProcessor,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @param UserApiResource $data
|
||||
* @param UserUpdationDto $data
|
||||
*
|
||||
* @return UserApiResource
|
||||
* @return UserUpdationDto
|
||||
*/
|
||||
public function process(mixed $data, API\Operation $operation, array $uriVariables = [], array $context = [])
|
||||
{
|
||||
$user = $this->userRepository->find($context['previous_data']->id);
|
||||
if (!$user) {
|
||||
throw new NotFoundHttpException();
|
||||
}
|
||||
|
||||
/** @var User */
|
||||
$user = $this->autoMapper->map($data, User::class);
|
||||
$user = $this->autoMapper->map($data, $user);
|
||||
|
||||
if (isset($data->password)) {
|
||||
$user->setPassword($this->userPasswordHasher->hashPassword($user, $data->password));
|
||||
}
|
||||
|
||||
if ($user->isType(UserType::Organization) && $user->getOrganization() === null) {
|
||||
$user->setOrganization(Organization::for($user));
|
||||
}
|
||||
|
||||
$user = $this->persistProcessor->process($user, $operation, $uriVariables, $context);
|
||||
$user = $this->entityProcessor->process($user, $operation, $uriVariables, $context);
|
||||
|
||||
if ($operation instanceof DeleteOperationInterface) {
|
||||
return;
|
||||
}
|
||||
|
||||
return $this->autoMapper->map($user, $data);
|
||||
return $this->autoMapper->map($user, UserApiResource::class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Validator;
|
||||
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
|
||||
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
|
||||
final class NotExisting extends Constraint
|
||||
{
|
||||
public string $message = 'The {{ property }} \'{{ value }}\' is already in use for {{ entity }} records.';
|
||||
|
||||
public function __construct(
|
||||
/**
|
||||
* FQCN of the entity to check uniqueness for.
|
||||
*/
|
||||
public string $entity,
|
||||
/**
|
||||
* Name of the property in the `entity` for which to check uniqueness.
|
||||
*/
|
||||
public string $property,
|
||||
?array $groups = null,
|
||||
mixed $payload = null,
|
||||
) {
|
||||
parent::__construct([], $groups, $payload);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Validator;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
|
||||
final class NotExistingValidator extends ConstraintValidator
|
||||
{
|
||||
public function __construct(
|
||||
private EntityManagerInterface $entityManager,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @param NotExisting $constraint
|
||||
*/
|
||||
public function validate(mixed $value, Constraint $constraint): void
|
||||
{
|
||||
$entityClass = $constraint->entity;
|
||||
$repository = $this->entityManager->getRepository($entityClass);
|
||||
|
||||
if (
|
||||
$value === null
|
||||
|| $value === ''
|
||||
|| $repository->findOneBy([$constraint->property => $value]) === null
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->context->buildViolation($constraint->message)
|
||||
->setParameter('{{ property }}', $constraint->property)
|
||||
->setParameter('{{ value }}', $value)
|
||||
->setParameter('{{ entity }}', $this->getClassShortName($entityClass))
|
||||
->addViolation()
|
||||
;
|
||||
}
|
||||
|
||||
private function getClassShortName(string $class): string
|
||||
{
|
||||
$reflect = new \ReflectionClass($class);
|
||||
|
||||
return $reflect->getShortName();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Validator;
|
||||
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
|
||||
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
|
||||
final class UserOwned extends Constraint
|
||||
{
|
||||
public string $message = 'Tried to reference "{{ value }}" while not authenticated as the very User.';
|
||||
|
||||
public function __construct(
|
||||
public string $mode = 'strict',
|
||||
?array $groups = null,
|
||||
mixed $payload = null,
|
||||
) {
|
||||
parent::__construct([], $groups, $payload);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace App\Validator;
|
||||
|
||||
use ApiPlatform\Metadata\IriConverterInterface;
|
||||
use App\ApiResource\User\UserApiResource;
|
||||
use App\Mapping\AutoMapper;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
|
||||
final class UserOwnedValidator extends ConstraintValidator
|
||||
{
|
||||
public function __construct(
|
||||
private Security $security,
|
||||
private AutoMapper $mapper,
|
||||
private IriConverterInterface $iriConverter,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @param UserOwned $constraint
|
||||
*/
|
||||
public function validate(mixed $value, Constraint $constraint): void
|
||||
{
|
||||
if ($value === null || $value === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
$value = $this->iriConverter->getIriFromResource($value);
|
||||
$user = $this->security->getUser();
|
||||
|
||||
if ($value && $user) {
|
||||
/** @var UserApiResource */
|
||||
$resource = $this->mapper->map($user, UserApiResource::class);
|
||||
$resourceIri = $this->iriConverter->getIriFromResource($resource);
|
||||
|
||||
if ($value === $resourceIri) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$this->context->buildViolation($constraint->message)
|
||||
->setParameter('{{ value }}', $value)
|
||||
->addViolation()
|
||||
;
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,7 @@
|
||||
<li>
|
||||
<div class="bg-white border border-grey rounded-2xl p-4 shadow-sm">
|
||||
<a href="{{ path('oauth_providers_start', { provider: provider }) }}">
|
||||
<img class="max-h-12 h-full" src="/oauth/providers/{{ provider }}.png" alt={{ provider }}>
|
||||
<img class="max-h-12 h-full" src="/oauth2/providers/{{ provider }}.png" alt={{ provider }}>
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
@@ -8,6 +8,7 @@ use App\Entity\Project\ProjectDeadline;
|
||||
use App\Entity\Project\ProjectStatus;
|
||||
use App\Entity\User\User;
|
||||
use App\Factory\Project\ProjectFactory;
|
||||
use App\Service\Project\ProjectService;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Zenstruck\Foundry\Test\Factories;
|
||||
use Zenstruck\Foundry\Test\ResetDatabase;
|
||||
@@ -18,12 +19,14 @@ class ProjectCalendarTest extends ApiTestCase
|
||||
use Factories;
|
||||
|
||||
private EntityManagerInterface $entityManager;
|
||||
private ProjectService $projectService;
|
||||
private User $owner;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
self::bootKernel();
|
||||
$this->entityManager = static::getContainer()->get(EntityManagerInterface::class);
|
||||
$this->projectService = static::getContainer()->get(ProjectService::class);
|
||||
|
||||
$this->owner = $this->createTestUser();
|
||||
$this->entityManager->persist($this->owner);
|
||||
@@ -44,6 +47,7 @@ class ProjectCalendarTest extends ApiTestCase
|
||||
{
|
||||
return ProjectFactory::createOne([
|
||||
'owner' => $this->owner,
|
||||
'status' => ProjectStatus::ToCampaign,
|
||||
'deadline' => $deadline,
|
||||
])->_real();
|
||||
}
|
||||
@@ -56,7 +60,9 @@ class ProjectCalendarTest extends ApiTestCase
|
||||
$this->entityManager->persist($project);
|
||||
$this->entityManager->flush();
|
||||
|
||||
$project->setStatus(ProjectStatus::InCampaign);
|
||||
$project = $this->projectService->transition($project, ProjectStatus::InCampaign);
|
||||
|
||||
$this->entityManager->persist($project);
|
||||
$this->entityManager->flush();
|
||||
|
||||
return $project;
|
||||
@@ -65,8 +71,11 @@ class ProjectCalendarTest extends ApiTestCase
|
||||
public function testReleaseDateUpdatesOnCampaignStart(): void
|
||||
{
|
||||
$project = $this->createProjectAndSetToInCampaign();
|
||||
$today = new \DateTime();
|
||||
$today->setTime(0, 0, 0, 0);
|
||||
|
||||
$this->assertNotNull($project->getCalendar()->release);
|
||||
$this->assertGreaterThan($today, $project->getCalendar()->release);
|
||||
}
|
||||
|
||||
public function testMinimumDeadlineIsSetCorrectly(): void
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Entity;
|
||||
|
||||
use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
|
||||
use App\Entity\Project\Project;
|
||||
use App\Entity\Project\ProjectDeadline;
|
||||
use App\Entity\Project\ProjectStatus;
|
||||
use App\Entity\Project\Review;
|
||||
use App\Entity\Project\ReviewType;
|
||||
use App\Entity\User\User;
|
||||
use App\Factory\Project\ProjectFactory;
|
||||
use App\Service\Project\ProjectService;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Zenstruck\Foundry\Test\Factories;
|
||||
use Zenstruck\Foundry\Test\ResetDatabase;
|
||||
|
||||
class ProjectCampaignReviewTest extends ApiTestCase
|
||||
{
|
||||
use ResetDatabase;
|
||||
use Factories;
|
||||
|
||||
private EntityManagerInterface $entityManager;
|
||||
private ProjectService $projectService;
|
||||
private User $owner;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
self::bootKernel();
|
||||
$this->entityManager = static::getContainer()->get(EntityManagerInterface::class);
|
||||
$this->projectService = static::getContainer()->get(ProjectService::class);
|
||||
|
||||
$this->owner = $this->createTestUser();
|
||||
$this->entityManager->persist($this->owner);
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
|
||||
private function createTestUser(string $handle = 'test_user', string $email = 'testuser@example.com'): User
|
||||
{
|
||||
$user = new User();
|
||||
$user->setHandle($handle);
|
||||
$user->setEmail($email);
|
||||
$user->setPassword('projectapitestpassword');
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
private function createTestProject(ProjectDeadline $deadline = ProjectDeadline::Minimum): Project
|
||||
{
|
||||
return ProjectFactory::createOne([
|
||||
'owner' => $this->owner,
|
||||
'deadline' => $deadline,
|
||||
'status' => ProjectStatus::InDraft,
|
||||
])->_real();
|
||||
}
|
||||
|
||||
private function createProjectAndSetToInCampaignReview(): Project
|
||||
{
|
||||
$project = $this->createTestProject();
|
||||
|
||||
$this->entityManager->persist($project);
|
||||
$this->entityManager->flush();
|
||||
|
||||
$project = $this->projectService->transition($project, ProjectStatus::ToCampaignReview);
|
||||
|
||||
$this->entityManager->persist($project);
|
||||
$this->entityManager->flush();
|
||||
|
||||
return $project;
|
||||
}
|
||||
|
||||
public function testCreatesReviewOnStatusChange(): void
|
||||
{
|
||||
$project = $this->createProjectAndSetToInCampaignReview();
|
||||
$reviews = $this->entityManager->getRepository(Review::class)->findBy(['project' => $project->getId()]);
|
||||
|
||||
$this->assertNotEmpty($reviews);
|
||||
$this->assertEquals(1, \count($reviews));
|
||||
$this->assertEquals(ReviewType::Campaign->value, $reviews[0]->getType()->value);
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace App\Tests\Service\Scout;
|
||||
|
||||
use App\Service\Scout\FileUriException;
|
||||
use App\Service\Scout\InvalidUriException;
|
||||
use App\Service\Scout\NonCrawlableUriException;
|
||||
use App\Service\Scout\ScoutService;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
|
||||
@@ -38,17 +38,17 @@ class ScoutServiceTest extends KernelTestCase
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providePathsToBinaryFiles
|
||||
* @dataProvider provideNonCrawlablePaths
|
||||
*/
|
||||
public function testThrowsExceptionOnPathsToBinaryFiles(string $url)
|
||||
public function testThrowsExceptionOnNonCrawlablePaths(string $url)
|
||||
{
|
||||
$this->expectException(FileUriException::class);
|
||||
$this->expectExceptionMessageMatches(\sprintf('/\(%s\)$/', \preg_quote($url, '/')));
|
||||
$this->expectException(NonCrawlableUriException::class);
|
||||
$this->expectExceptionMessageMatches(\sprintf('/%s/', \preg_quote($url, '/')));
|
||||
|
||||
$this->scout->get($url);
|
||||
}
|
||||
|
||||
public function providePathsToBinaryFiles(): array
|
||||
public function provideNonCrawlablePaths(): array
|
||||
{
|
||||
return [
|
||||
['https://sialmaraz.es/wp-content/uploads/2025/01/MANIFESTACION-ALMARAZ-18_1.mp4'],
|
||||
|
||||
Reference in New Issue
Block a user