diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..ec5b9bd4 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,14 @@ +/public/bundles/ +/var/ + +.phpunit.result.cache +/phpunit.xml + +/phpunit.xml +.phpunit.result.cache + +/.php-cs-fixer.php +/.php-cs-fixer.cache +.git/ +.github/ +.vscode/ diff --git a/.env b/.env index ff59b199..fbd996bc 100644 --- a/.env +++ b/.env @@ -51,3 +51,9 @@ PAYPAL_CLIENT_ID="" PAYPAL_CLIENT_SECRET="" PAYPAL_WEBHOOK_ID="WEBHOOK_ID" ###< App\Gateway\Paypal\PaypalGateway ### + +###> symfony/lock ### +# Choose one of the stores below +# postgresql+advisory://db_user:db_password@localhost/db_name +LOCK_DSN=flock +###< symfony/lock ### diff --git a/.github/workflows/php-cs-fixer.yaml b/.github/workflows/php-cs-fixer.yaml index 1c3dea88..c0f0dd79 100644 --- a/.github/workflows/php-cs-fixer.yaml +++ b/.github/workflows/php-cs-fixer.yaml @@ -4,10 +4,10 @@ jobs: php-cs-fixer: runs-on: "ubuntu-latest" steps: - - name: Setup PHP 8.2 + - name: Setup PHP 8.3 uses: shivammathur/setup-php@v2 with: - php-version: '8.2' + php-version: '8.3' tools: php-cs-fixer:3.64 - name: Checkout repository diff --git a/.github/workflows/phpunit.yaml b/.github/workflows/phpunit.yaml index 2d80aba2..0122ac8b 100644 --- a/.github/workflows/phpunit.yaml +++ b/.github/workflows/phpunit.yaml @@ -18,14 +18,13 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Install dependencies - run: composer install --prefer-dist --no-progress --no-suggest + - uses: php-actions/composer@v6 - name: PHPUnit uses: php-actions/phpunit@v4 env: DATABASE_URL: mysql://root:goteo@127.0.0.1:${{ job.services.mariadb.ports['3306'] }}/goteo with: - php_version: 8.2 + php_version: 8.3 php_extensions: pdo_mysql configuration: phpunit.xml.dist diff --git a/composer.json b/composer.json index 352c5bc5..a05e6977 100644 --- a/composer.json +++ b/composer.json @@ -16,6 +16,7 @@ "doctrine/doctrine-migrations-bundle": "^3.3", "doctrine/orm": "^2.19", "doctrineencryptbundle/doctrine-encrypt-bundle": "^5.4", + "jolicode/automapper": "^9.2", "nelmio/cors-bundle": "^2.3", "phpdocumentor/reflection-docblock": "^5.3", "phpstan/phpdoc-parser": "^1.24", diff --git a/composer.lock b/composer.lock index 184b445a..4ade9636 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "12528cb2aa86e56aac757107d3c97554", + "content-hash": "606fea65b8befe4e0c0a4698dd4b24d7", "packages": [ { "name": "api-platform/core", @@ -2052,6 +2052,159 @@ ], "time": "2024-10-07T22:30:27+00:00" }, + { + "name": "godruoyi/php-snowflake", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/godruoyi/php-snowflake.git", + "reference": "cbf44765679bd1e58f1c2ec0263eb340dd0d6f47" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/godruoyi/php-snowflake/zipball/cbf44765679bd1e58f1c2ec0263eb340dd0d6f47", + "reference": "cbf44765679bd1e58f1c2ec0263eb340dd0d6f47", + "shasum": "" + }, + "require": { + "php-64bit": ">=8.1" + }, + "require-dev": { + "ext-redis": "*", + "ext-swoole": "*", + "illuminate/contracts": "^10.0 || ^11.0", + "laravel/pint": "^1.10", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^10", + "predis/predis": "^2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Godruoyi\\Snowflake\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Godruoyi", + "email": "g@godruoyi.com" + } + ], + "description": "An ID Generator for PHP based on Snowflake Algorithm (Twitter announced).", + "homepage": "https://github.com/godruoyi/php-snowflake", + "keywords": [ + "Unique ID", + "laravel snowflake", + "order id", + "php snowflake", + "php sonyflake", + "php unique id", + "snowflake algorithm", + "sonyflake", + "unique order id" + ], + "support": { + "issues": "https://github.com/godruoyi/php-snowflake/issues", + "source": "https://github.com/godruoyi/php-snowflake/tree/3.1.0" + }, + "funding": [ + { + "url": "https://images.godruoyi.com/wechat.png", + "type": "custom" + } + ], + "time": "2024-04-08T07:08:02+00:00" + }, + { + "name": "jolicode/automapper", + "version": "9.2.0", + "source": { + "type": "git", + "url": "https://github.com/jolicode/automapper.git", + "reference": "ce0025d4d312fc443efe7c4ccf93d1381576d262" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jolicode/automapper/zipball/ce0025d4d312fc443efe7c4ccf93d1381576d262", + "reference": "ce0025d4d312fc443efe7c4ccf93d1381576d262", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": "^8.2", + "phpdocumentor/type-resolver": "^1.7", + "phpstan/phpdoc-parser": "^1.13 || ^2.0", + "symfony/deprecation-contracts": "^3.0", + "symfony/event-dispatcher": "^6.4 || ^7.0", + "symfony/expression-language": "^6.4 || ^7.0", + "symfony/lock": "^6.4 || ^7.0", + "symfony/property-access": "^6.4 || ^7.0", + "symfony/property-info": "^6.4 || ^7.0" + }, + "conflict": { + "api-platform/core": "<3", + "symfony/framework-bundle": "<6.4", + "symfony/serializer": "<6.4" + }, + "require-dev": { + "api-platform/core": "^3.0.4", + "doctrine/annotations": "~1.0", + "doctrine/inflector": "^2.0", + "matthiasnoback/symfony-dependency-injection-test": "^5.1", + "moneyphp/money": "^3.3.2", + "phpunit/phpunit": "^9.0", + "symfony/browser-kit": "^6.4 || ^7.0", + "symfony/console": "^6.4 || ^7.0", + "symfony/filesystem": "^6.4 || ^7.0", + "symfony/framework-bundle": "*", + "symfony/http-client": "^6.4 || ^7.0", + "symfony/http-kernel": "^6.4 || ^7.0", + "symfony/serializer": "*", + "symfony/stopwatch": "^6.4 || ^7.0", + "symfony/twig-bundle": "^6.4 || ^7.0", + "symfony/uid": "^6.4 || ^7.0", + "symfony/web-profiler-bundle": "^6.4 || ^7.0", + "symfony/yaml": "^6.4 || ^7.0", + "willdurand/negotiation": "^3.0" + }, + "suggest": { + "symfony/serializer": "Allow to use symfony serializer attributes in mapping" + }, + "type": "library", + "autoload": { + "files": [ + "src/php-parser.php" + ], + "psr-4": { + "AutoMapper\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Joel Wurtz", + "email": "jwurtz@jolicode.com" + }, + { + "name": "Baptiste Leduc", + "email": "baptiste.leduc@gmail.com" + } + ], + "description": "JoliCode AutoMapper", + "homepage": "https://github.com/jolicode/automapper", + "support": { + "issues": "https://github.com/jolicode/automapper/issues", + "source": "https://github.com/jolicode/automapper/tree/9.2.0" + }, + "time": "2024-11-19T17:21:31+00:00" + }, { "name": "monolog/monolog", "version": "3.7.0", @@ -2215,6 +2368,64 @@ }, "time": "2024-06-24T21:25:28+00:00" }, + { + "name": "nikic/php-parser", + "version": "v5.3.1", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/8eea230464783aa9671db8eea6f8c6ac5285794b", + "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.3.1" + }, + "time": "2024-10-08T18:51:32+00:00" + }, { "name": "paragonie/constant_time_encoding", "version": "v3.0.0", @@ -5063,6 +5274,85 @@ ], "time": "2024-09-20T08:16:53+00:00" }, + { + "name": "symfony/lock", + "version": "v6.4.13", + "source": { + "type": "git", + "url": "https://github.com/symfony/lock.git", + "reference": "a69c3dd151ab7e14925f119164cfdf65d55392a4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/lock/zipball/a69c3dd151ab7e14925f119164cfdf65d55392a4", + "reference": "a69c3dd151ab7e14925f119164cfdf65d55392a4", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "doctrine/dbal": "<2.13", + "symfony/cache": "<6.2" + }, + "require-dev": { + "doctrine/dbal": "^2.13|^3|^4", + "predis/predis": "^1.1|^2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Lock\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jérémy Derussé", + "email": "jeremy@derusse.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Creates and manages locks, a mechanism to provide exclusive access to a shared resource", + "homepage": "https://symfony.com", + "keywords": [ + "cas", + "flock", + "locking", + "mutex", + "redlock", + "semaphore" + ], + "support": { + "source": "https://github.com/symfony/lock/tree/v6.4.13" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-10-25T15:19:46+00:00" + }, { "name": "symfony/monolog-bridge", "version": "v6.4.8", @@ -8186,64 +8476,6 @@ ], "time": "2024-06-12T14:39:25+00:00" }, - { - "name": "nikic/php-parser", - "version": "v5.3.1", - "source": { - "type": "git", - "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/8eea230464783aa9671db8eea6f8c6ac5285794b", - "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b", - "shasum": "" - }, - "require": { - "ext-ctype": "*", - "ext-json": "*", - "ext-tokenizer": "*", - "php": ">=7.4" - }, - "require-dev": { - "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^9.0" - }, - "bin": [ - "bin/php-parse" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0-dev" - } - }, - "autoload": { - "psr-4": { - "PhpParser\\": "lib/PhpParser" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov" - } - ], - "description": "A PHP parser written in PHP", - "keywords": [ - "parser", - "php" - ], - "support": { - "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.3.1" - }, - "time": "2024-10-08T18:51:32+00:00" - }, { "name": "phar-io/manifest", "version": "2.0.4", @@ -11041,7 +11273,7 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": {}, + "stability-flags": [], "prefer-stable": true, "prefer-lowest": false, "platform": { @@ -11049,6 +11281,6 @@ "ext-ctype": "*", "ext-iconv": "*" }, - "platform-dev": {}, - "plugin-api-version": "2.6.0" + "platform-dev": [], + "plugin-api-version": "2.2.0" } diff --git a/config/bundles.php b/config/bundles.php index 19b2cdae..9dbe7c21 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -13,4 +13,5 @@ Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true], Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true], Ambta\DoctrineEncryptBundle\AmbtaDoctrineEncryptBundle::class => ['all' => true], + AutoMapper\Symfony\Bundle\AutoMapperBundle::class => ['all' => true], ]; diff --git a/config/packages/lock.yaml b/config/packages/lock.yaml new file mode 100644 index 00000000..574879f8 --- /dev/null +++ b/config/packages/lock.yaml @@ -0,0 +1,2 @@ +framework: + lock: '%env(LOCK_DSN)%' diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 6babe2dc..1c6454f2 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -12,7 +12,7 @@ security: # used to reload user from session & other features (e.g. switch_user) app_user_provider: entity: - class: App\Entity\User + class: App\Entity\User\User property: username firewalls: dev: diff --git a/config/services.yaml b/config/services.yaml index 3b5579e2..654922da 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -18,6 +18,8 @@ services: tags: ['app.lib.benzina.pump.pump'] App\Library\Economy\Currency\ExchangeInterface: tags: ['app.lib.economy.currency.exchange'] + AutoMapper\Provider\ProviderInterface: + tags: ['app.mapping.map_provider'] # makes classes in src/ available to be used as services # this creates a service per class whose id is the fully-qualified class name @@ -65,5 +67,9 @@ services: arguments: - !tagged 'app.lib.economy.currency.exchange' + App\Mapping\AutoMapper: + arguments: + - !tagged 'app.mapping.map_provider' + # add more service definitions when explicit configuration is needed # please note that last definitions always *replace* previous ones diff --git a/docker-compose.override.yml b/docker-compose.override.yml index d9655686..5c296844 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -6,3 +6,6 @@ services: extra_hosts: - "host.docker.internal:host-gateway" user: "${UID:-1000}:${GID:-1000}" + ports: + - "9000:9000" + - "9003:9003" diff --git a/docker-compose.yml b/docker-compose.yml index 3e8c8a7e..8bfe0028 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,9 @@ services: php: - build: docker/php + build: + context: . + dockerfile: docker/php/Dockerfile + target: dev container_name: goteo-v4-php volumes: - .:/app @@ -14,7 +17,9 @@ services: - ./docker/nginx/nginx.conf:/etc/nginx/conf.d/default.conf ports: - "${APP_HTTP_PORT:-8090}:80" - - "${APP_HTTPS_PORT:-8091}:433" + - "${APP_HTTPS_PORT:-8091}:443" + depends_on: + - php mariadb: image: mariadb:10.11.2 @@ -28,4 +33,4 @@ services: MYSQL_USER: ${DB_USER:-goteo} volumes: - mariadb-data: \ No newline at end of file + mariadb-data: diff --git a/docker/php/Dockerfile b/docker/php/Dockerfile index 4641cb4e..decc4a96 100644 --- a/docker/php/Dockerfile +++ b/docker/php/Dockerfile @@ -1,29 +1,51 @@ -FROM php:8.3-fpm +FROM php:8.3-fpm-alpine AS base -# System dependencies -RUN apt-get update && apt install -y curl git +RUN apk --no-cache add \ + curl \ + git \ + linux-headers \ + icu-dev \ + zip \ + libzip-dev \ + unzip -# PHP extensions +RUN docker-php-ext-install \ + pdo \ + pdo_mysql + +RUN docker-php-ext-install \ + intl \ + zip + +COPY --from=composer:lts /usr/bin/composer /usr/local/bin/composer + + +FROM base AS dev -## OPcache RUN docker-php-ext-install opcache -## XDebug +RUN apk --no-cache add $PHPIZE_DEPS RUN pecl install xdebug \ && docker-php-ext-enable xdebug -## PDO & mysql -RUN docker-php-ext-install pdo pdo_mysql +COPY . /app +RUN chown -R www-data:www-data /app -# Zip -RUN apt-get install -y libzip-dev zip unzip \ - && docker-php-ext-install zip +USER www-data +WORKDIR /app -# Intl -RUN apt-get install -y libicu-dev \ - && docker-php-ext-install intl +RUN composer install --prefer-dist --no-scripts -# Composer -COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer +FROM base AS prod +COPY . /app +RUN chown -R www-data:www-data /app + +USER www-data WORKDIR /app + +RUN composer install --prefer-dist --no-scripts --no-dev --optimize-autoloader + +EXPOSE 9000 + +CMD ["php-fpm"] diff --git a/src/ApiResource/Accounting/Accounting.php b/src/ApiResource/Accounting/Accounting.php deleted file mode 100644 index 2bf2fc6a..00000000 --- a/src/ApiResource/Accounting/Accounting.php +++ /dev/null @@ -1,37 +0,0 @@ -ownerClass) { + case User::class: + return $this->user; + case Project::class: + return $this->project; + case Tipjar::class: + return $this->tipjar; + } + } + + /** + * The money currently held by the Accounting. + */ + public Money $balance; +} diff --git a/src/ApiResource/Gateway/Charge.php b/src/ApiResource/Gateway/ChargeApiResource.php similarity index 82% rename from src/ApiResource/Gateway/Charge.php rename to src/ApiResource/Gateway/ChargeApiResource.php index 7252f2b6..a3e6dd76 100644 --- a/src/ApiResource/Gateway/Charge.php +++ b/src/ApiResource/Gateway/ChargeApiResource.php @@ -4,11 +4,11 @@ use ApiPlatform\Doctrine\Orm\State\Options; use ApiPlatform\Metadata as API; -use App\ApiResource\Accounting\Accounting; -use App\Entity\Gateway\Charge as EntityCharge; +use App\ApiResource\Accounting\AccountingApiResource; +use App\Entity\Gateway\Charge; use App\Entity\Money; use App\Gateway\ChargeType; -use App\State\Gateway\ChargeStateProvider; +use App\State\ApiResourceStateProvider; use Symfony\Component\Validator\Constraints as Assert; /** @@ -16,11 +16,11 @@ */ #[API\ApiResource( shortName: 'GatewayCharge', - stateOptions: new Options(entityClass: EntityCharge::class), - provider: ChargeStateProvider::class + stateOptions: new Options(entityClass: Charge::class), + provider: ApiResourceStateProvider::class )] #[API\Get()] -class Charge +class ChargeApiResource { #[API\ApiProperty(writable: false, identifier: true)] public ?int $id = null; @@ -51,7 +51,7 @@ class Charge * The Accounting receiving the money after a successful payment. */ #[Assert\NotBlank()] - public Accounting $target; + public AccountingApiResource $target; /** * The money to-be-paid for this item at the Gateway. diff --git a/src/ApiResource/Gateway/Checkout.php b/src/ApiResource/Gateway/CheckoutApiResource.php similarity index 66% rename from src/ApiResource/Gateway/Checkout.php rename to src/ApiResource/Gateway/CheckoutApiResource.php index f50e219f..a1bc8380 100644 --- a/src/ApiResource/Gateway/Checkout.php +++ b/src/ApiResource/Gateway/CheckoutApiResource.php @@ -4,13 +4,14 @@ use ApiPlatform\Doctrine\Orm\State\Options; use ApiPlatform\Metadata as API; -use App\ApiResource\Accounting\Accounting; -use App\Entity\Gateway as Entity; +use App\ApiResource\Accounting\AccountingApiResource; +use App\Entity\Gateway\Checkout; use App\Gateway\CheckoutStatus; use App\Gateway\Link; use App\Gateway\Tracking; +use App\State\ApiResourceStateProvider; use App\State\Gateway\CheckoutStateProcessor; -use App\State\Gateway\CheckoutStateProvider; +use AutoMapper\Attribute\MapFrom; use Symfony\Component\Validator\Constraints as Assert; /** @@ -18,34 +19,34 @@ */ #[API\ApiResource( shortName: 'GatewayCheckout', - stateOptions: new Options(entityClass: Entity\Checkout::class), - provider: CheckoutStateProvider::class, + stateOptions: new Options(entityClass: Checkout::class), + provider: ApiResourceStateProvider::class, processor: CheckoutStateProcessor::class, )] #[API\GetCollection()] #[API\Post()] #[API\Get()] -class Checkout +class CheckoutApiResource { #[API\ApiProperty(writable: false, identifier: true)] - public ?int $id = null; + public int $id; /** * The desired Gateway to checkout with. */ #[Assert\NotBlank()] - public Gateway $gateway; + public GatewayApiResource $gateway; /** * The Accounting paying for the charges. */ #[Assert\NotBlank()] - public Accounting $origin; + public AccountingApiResource $origin; /** * A list of the payment items to be charged to the origin. * - * @var Charge[] + * @var ChargeApiResource[] */ #[API\ApiProperty(readableLink: true, writableLink: true)] #[Assert\NotBlank()] @@ -72,5 +73,17 @@ class Checkout * @var Tracking[] */ #[API\ApiProperty(writable: false)] + #[MapFrom(transformer: [self::class, 'parseTrackings'])] public array $trackings = []; + + public static function parseTrackings(array $values) + { + return \array_map(function ($value) { + $tracking = new Tracking(); + $tracking->title = $value['title']; + $tracking->value = $value['value']; + + return $tracking; + }, $values); + } } diff --git a/src/ApiResource/Gateway/Gateway.php b/src/ApiResource/Gateway/GatewayApiResource.php similarity index 76% rename from src/ApiResource/Gateway/Gateway.php rename to src/ApiResource/Gateway/GatewayApiResource.php index c896c695..e94cd81c 100644 --- a/src/ApiResource/Gateway/Gateway.php +++ b/src/ApiResource/Gateway/GatewayApiResource.php @@ -12,16 +12,19 @@ * These implementations make use of external or internal services to gather the funds that are inside a Transaction, * perform corroboration of funds and store the Transactions into the system. */ -#[API\ApiResource()] -#[API\GetCollection(provider: GatewayStateProvider::class)] -#[API\Get(provider: GatewayStateProvider::class)] -class Gateway +#[API\ApiResource( + shortName: 'Gateway', + provider: GatewayStateProvider::class +)] +#[API\GetCollection()] +#[API\Get()] +class GatewayApiResource { #[API\ApiProperty(identifier: true)] - public ?string $name = null; + public string $name; /** * @var array */ - public ?array $supports = null; + public array $supports; } diff --git a/src/ApiResource/Project/ProjectApiResource.php b/src/ApiResource/Project/ProjectApiResource.php new file mode 100644 index 00000000..235bb21e --- /dev/null +++ b/src/ApiResource/Project/ProjectApiResource.php @@ -0,0 +1,67 @@ + + */ + public array $rewards; +} diff --git a/src/ApiResource/Project/RewardApiResource.php b/src/ApiResource/Project/RewardApiResource.php new file mode 100644 index 00000000..bf340272 --- /dev/null +++ b/src/ApiResource/Project/RewardApiResource.php @@ -0,0 +1,73 @@ + + */ + #[API\ApiProperty(securityPostDenormalize: 'is_granted("ROLE_ADMIN")')] + public array $roles; + + /** + * The Accounting for this User monetary movements. + */ + #[API\ApiProperty(writable: false)] + public AccountingApiResource $accounting; + + /** + * The Projects that are owned by this User. + * + * @var array + */ + #[API\ApiProperty(writable: false)] + public array $projects; +} diff --git a/src/ApiResource/User/UserTokenApiResource.php b/src/ApiResource/User/UserTokenApiResource.php new file mode 100644 index 00000000..bdc31bdd --- /dev/null +++ b/src/ApiResource/User/UserTokenApiResource.php @@ -0,0 +1,36 @@ +setOwner($owner); + + return $accounting; + } + public function __construct() { /* @@ -69,34 +80,50 @@ public function setCurrency(string $currency): static return $this; } - public function getOwner(): ?AccountingOwnerInterface + public function getOwnerClass(): ?string { - $owner = $this->owner; - - return $this->$owner; + return $this->ownerClass; } - public function setOwner(AccountingOwnerInterface $owner): static + public function setOwnerClass(string $ownerClass): static { - if ($owner instanceof User) { - $this->owner = 'user'; + $this->ownerClass = $ownerClass; - return $this->setUser($owner); + return $this; + } + + public function getOwner(): ?AccountingOwnerInterface + { + switch ($this->getOwnerClass()) { + case User::class: + return $this->getUser(); + case Project::class: + return $this->getProject(); + case Tipjar::class: + return $this->getTipjar(); } - if ($owner instanceof Project) { - $this->owner = 'project'; + return null; + } - return $this->setProject($owner); + public function setOwner(?AccountingOwnerInterface $owner): static + { + if ($owner === null) { + return $this; } - if ($owner instanceof Tipjar) { - $this->owner = 'tipjar'; + $this->setOwnerClass($owner::class); - return $this->setTipjar($owner); + switch ($this->getOwnerClass()) { + case User::class: + return $this->setUser($owner); + case Project::class: + return $this->setProject($owner); + case Tipjar::class: + return $this->setTipjar($owner); } - throw new \Exception(sprintf('%s is not a recognized AccountingOwnerInterface', $owner::class)); + return $this; } public function getUser(): ?User diff --git a/src/Entity/Interface/UserOwnedInterface.php b/src/Entity/Interface/UserOwnedInterface.php index 076b4b32..e2d7408e 100644 --- a/src/Entity/Interface/UserOwnedInterface.php +++ b/src/Entity/Interface/UserOwnedInterface.php @@ -2,11 +2,13 @@ namespace App\Entity\Interface; -use App\Entity\User; +use App\Entity\User\User; interface UserOwnedInterface { public function getOwner(): ?User; public function isOwnedBy(User $user): bool; + + public function setOwner(User $user): static; } diff --git a/src/Entity/Project.php b/src/Entity/Project/Project.php similarity index 60% rename from src/Entity/Project.php rename to src/Entity/Project/Project.php index 1609ad3f..d91b71ad 100644 --- a/src/Entity/Project.php +++ b/src/Entity/Project/Project.php @@ -1,32 +1,21 @@ 'partial', - 'status' => 'exact', - 'owner' => 'exact', -])] #[ORM\Entity(repositoryClass: ProjectRepository::class)] -class Project implements AccountingOwnerInterface +class Project implements UserOwnedInterface, AccountingOwnerInterface { use MigratedEntity; use TimestampedCreationEntity; @@ -47,15 +36,13 @@ class Project implements AccountingOwnerInterface * Since Projects can be recipients of funding, they are assigned an Accounting when created. * A Project's Accounting represents how much money the Project has raised from the community. */ - #[API\ApiProperty(writable: false)] #[ORM\OneToOne(inversedBy: 'project', cascade: ['persist'])] private ?Accounting $accounting = null; /** * The User who created this Project. */ - #[API\ApiProperty(writable: false)] - #[ORM\ManyToOne(inversedBy: 'projects')] + #[ORM\ManyToOne(inversedBy: 'projects', cascade: ['persist'])] #[ORM\JoinColumn(nullable: false)] private ?User $owner = null; @@ -63,16 +50,19 @@ class Project implements AccountingOwnerInterface * The status of this Project as it goes through it's life-cycle. * Projects have a start and an end, and in the meantime they go through different phases represented under this status. */ - #[API\ApiProperty(writable: true)] #[ORM\Column(type: 'string', enumType: ProjectStatus::class)] private ProjectStatus $status; + /** + * @var Collection + */ + #[ORM\OneToMany(mappedBy: 'project', targetEntity: Reward::class)] + private Collection $rewards; + public function __construct() { - $accounting = new Accounting(); - $accounting->setOwner($this); - - $this->accounting = $accounting; + $this->accounting = Accounting::of($this); + $this->rewards = new ArrayCollection(); } public function getId(): ?int @@ -80,6 +70,13 @@ public function getId(): ?int return $this->id; } + public function setId(int $id): static + { + $this->id = $id; + + return $this; + } + public function getTitle(): ?string { return $this->title; @@ -109,6 +106,11 @@ public function getOwner(): ?User return $this->owner; } + public function isOwnedBy(User $user): bool + { + return $user->getId() === $this->owner->getId(); + } + public function setOwner(?User $owner): static { $this->owner = $owner; @@ -127,4 +129,34 @@ public function setStatus(ProjectStatus $status): static return $this; } + + /** + * @return Collection + */ + public function getRewards(): Collection + { + return $this->rewards; + } + + public function addReward(Reward $reward): static + { + if (!$this->rewards->contains($reward)) { + $this->rewards->add($reward); + $reward->setProject($this); + } + + return $this; + } + + public function removeReward(Reward $reward): static + { + if ($this->rewards->removeElement($reward)) { + // set the owning side to null (unless already changed) + if ($reward->getProject() === $this) { + $reward->setProject(null); + } + } + + return $this; + } } diff --git a/src/Entity/Project/ProjectStatus.php b/src/Entity/Project/ProjectStatus.php new file mode 100644 index 00000000..366b3219 --- /dev/null +++ b/src/Entity/Project/ProjectStatus.php @@ -0,0 +1,44 @@ + + */ + #[ORM\OneToMany(mappedBy: 'reward', targetEntity: RewardClaim::class)] + private Collection $claims; + + public function __construct() + { + $this->claims = 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 getTitle(): ?string + { + return $this->title; + } + + public function setTitle(string $title): static + { + $this->title = $title; + + return $this; + } + + public function getDescription(): ?string + { + return $this->description; + } + + public function setDescription(?string $description): static + { + $this->description = $description; + + return $this; + } + + public function getMoney(): ?Money + { + return $this->money; + } + + public function setMoney(Money $money): static + { + $this->money = $money; + + return $this; + } + + public function hasUnits(): bool + { + return $this->hasUnits; + } + + public function setHasUnits(bool $hasUnits): static + { + $this->hasUnits = $hasUnits; + + return $this; + } + + public function getUnitsTotal(): ?int + { + return $this->unitsTotal; + } + + public function setUnitsTotal(int $unitsTotal): static + { + $this->unitsTotal = $unitsTotal; + + return $this; + } + + public function getUnitsAvailable(): ?int + { + return $this->unitsAvailable; + } + + public function setUnitsAvailable(int $unitsAvailable): static + { + $this->unitsAvailable = $unitsAvailable; + + return $this; + } + + /** + * @return Collection + */ + public function getClaims(): Collection + { + return $this->claims; + } + + public function addClaim(RewardClaim $claim): static + { + if (!$this->claims->contains($claim)) { + $this->claims->add($claim); + $claim->setReward($this); + } + + return $this; + } + + public function removeClaim(RewardClaim $claim): static + { + if ($this->claims->removeElement($claim)) { + // set the owning side to null (unless already changed) + if ($claim->getReward() === $this) { + $claim->setReward(null); + } + } + + return $this; + } +} diff --git a/src/Entity/Project/RewardClaim.php b/src/Entity/Project/RewardClaim.php new file mode 100644 index 00000000..4dc14fd8 --- /dev/null +++ b/src/Entity/Project/RewardClaim.php @@ -0,0 +1,59 @@ +id; + } + + public function getReward(): ?Reward + { + return $this->reward; + } + + public function setReward(?Reward $reward): static + { + $this->reward = $reward; + + return $this; + } + + public function getOwner(): ?User + { + return $this->owner; + } + + public function isOwnedBy(User $user): bool + { + return $this->owner->getId() === $user->getId(); + } + + public function setOwner(?User $owner): static + { + $this->owner = $owner; + + return $this; + } +} diff --git a/src/Entity/ProjectStatus.php b/src/Entity/ProjectStatus.php deleted file mode 100644 index 04c5b815..00000000 --- a/src/Entity/ProjectStatus.php +++ /dev/null @@ -1,14 +0,0 @@ -setOwner($this); - - $this->accounting = $accounting; + $this->accounting = Accounting::of($this); } public function getId(): ?int diff --git a/src/Entity/Trait/MigratedEntity.php b/src/Entity/Trait/MigratedEntity.php index 07a31351..e8bd672c 100644 --- a/src/Entity/Trait/MigratedEntity.php +++ b/src/Entity/Trait/MigratedEntity.php @@ -3,6 +3,7 @@ namespace App\Entity\Trait; use ApiPlatform\Metadata as API; +use AutoMapper\Attribute\MapFrom; use Doctrine\ORM\Mapping as ORM; trait MigratedEntity @@ -18,6 +19,7 @@ trait MigratedEntity * Previous ID of the entity in the Goteo v3 platform. */ #[API\ApiProperty(writable: false)] + #[MapFrom(if: 'isMigrated')] #[ORM\Column(length: 255, nullable: true)] protected ?string $migratedId = null; diff --git a/src/Entity/User.php b/src/Entity/User/User.php similarity index 75% rename from src/Entity/User.php rename to src/Entity/User/User.php index 35d74b84..d913e884 100644 --- a/src/Entity/User.php +++ b/src/Entity/User/User.php @@ -1,17 +1,14 @@ ['default', 'postValidation']])] -#[API\Get()] -#[API\Put(security: 'is_granted("USER_EDIT", object)')] -#[API\Delete(security: 'is_granted("USER_EDIT", object)')] -#[API\Patch(security: 'is_granted("USER_EDIT", object)')] -#[API\ApiFilter(filterClass: UserQueryFilter::class, properties: ['query'])] -#[API\ApiFilter(filterClass: OrderedLikeFilter::class, properties: ['username'])] #[UniqueEntity(fields: ['username'], message: 'This usernames already exists.')] #[UniqueEntity(fields: ['email'], message: 'This email address is already registered.')] #[ORM\Entity(repositoryClass: UserRepository::class)] #[ORM\Index(fields: ['migratedId'])] -class User implements UserInterface, UserOwnedInterface, PasswordAuthenticatedUserInterface, AccountingOwnerInterface +class User implements UserInterface, PasswordAuthenticatedUserInterface, AccountingOwnerInterface { use MigratedEntity; use TimestampedCreationEntity; @@ -52,67 +39,47 @@ class User implements UserInterface, UserOwnedInterface, PasswordAuthenticatedUs #[ORM\Column] private ?int $id = null; - /** - * Human readable, non white space, unique string. - */ #[Gedmo\Versioned] - #[Assert\NotBlank()] - #[Assert\Length(min: 4, max: 30)] - #[Assert\Regex('/^[a-z0-9_]+$/')] #[ORM\Column(length: 255)] - private ?string $username = null; - - /** - * @var list The user roles. Admin only property. - */ - #[ORM\Column] - #[API\ApiProperty(security: 'is_granted("ROLE_ADMIN")')] - private array $roles = []; + private ?string $email = null; /** - * @var string The user password + * Has this User confirmed their email address? */ - #[API\ApiProperty(writable: false, readable: false)] #[ORM\Column] - private ?string $password = null; + private ?bool $emailConfirmed = null; /** - * Plain-text, will be hashed by the platform. + * Human readable, non white space, unique string. */ - #[Assert\NotBlank(['groups' => ['postValidation']])] - #[Assert\Length(min: 12)] - #[API\ApiProperty(writable: true, readable: false, required: true)] - #[SerializedName('password')] - private ?string $plainPassword = null; - #[Gedmo\Versioned] - #[Assert\NotBlank()] - #[Assert\Email()] #[ORM\Column(length: 255)] - private ?string $email = null; + private ?string $username = null; /** - * Has this User confirmed their email address? + * @var list The user roles. Admin only property. */ - #[API\ApiProperty(writable: false)] #[ORM\Column] - private ?bool $emailConfirmed = null; + private array $roles = []; - #[API\ApiProperty(writable: false)] #[ORM\OneToOne(inversedBy: 'user', cascade: ['persist'])] private ?Accounting $accounting = null; + /** + * The projects owned by this User. + */ + #[ORM\OneToMany(mappedBy: 'owner', targetEntity: Project::class)] + private Collection $projects; + /** * The UserTokens owned by this User. Owner only property. */ - #[API\ApiProperty(writable: false, security: 'is_granted("USER_OWNED", object)')] #[ORM\OneToMany(mappedBy: 'owner', targetEntity: UserToken::class, orphanRemoval: true)] private Collection $tokens; /** * A flag determined by the platform for Users who are known to be active. */ - #[API\ApiProperty(writable: false)] #[ORM\Column] private ?bool $active = null; @@ -128,22 +95,18 @@ class User implements UserInterface, UserOwnedInterface, PasswordAuthenticatedUs #[ORM\Column(length: 255, nullable: true)] private ?string $name = null; - /** - * The projects owned by this User. - */ - #[API\ApiProperty(writable: false)] - #[ORM\OneToMany(mappedBy: 'owner', targetEntity: Project::class)] - private Collection $projects; - #[ORM\OneToOne(mappedBy: 'user', cascade: ['persist', 'remove'])] private ?UserPersonal $personalData = null; + /** + * @var string The user password + */ + #[ORM\Column] + private ?string $password = null; + public function __construct() { - $accounting = new Accounting(); - $accounting->setOwner($this); - - $this->accounting = $accounting; + $this->accounting = Accounting::of($this); $this->emailConfirmed = false; $this->active = false; @@ -157,37 +120,50 @@ public function getId(): ?int return $this->id; } - public function getUsername(): ?string + /** + * A visual identifier that represents this user. + * + * @see UserInterface + */ + public function getUserIdentifier(): string { - return $this->username; + return (string) $this->username; } - public function setUsername(?string $username): static + public function getEmail(): ?string { - $this->username = strtolower($username); + return $this->email; + } + + public function setEmail(string $email): static + { + $this->email = $email; return $this; } - /** - * A visual identifier that represents this user. - * - * @see UserInterface - */ - #[API\ApiProperty(readable: false)] - public function getUserIdentifier(): string + public function isEmailConfirmed(): ?bool { - return (string) $this->username; + return $this->emailConfirmed; } - public function getOwner(): ?User + public function setEmailConfirmed(bool $emailConfirmed): static { + $this->emailConfirmed = $emailConfirmed; + return $this; } - public function isOwnedBy(User $user): bool + public function getUsername(): ?string + { + return $this->username; + } + + public function setUsername(?string $username): static { - return $this->getUserIdentifier() === $user->getUserIdentifier(); + $this->username = strtolower($username); + + return $this; } /** @@ -219,74 +195,44 @@ public function hasRoles(array $roles): bool return count(array_intersect($this->getRoles(), $roles)) > 0; } - /** - * @see PasswordAuthenticatedUserInterface - */ - public function getPassword(): string - { - return $this->password; - } - - public function setPassword(string $password): static - { - $this->password = $password; - - return $this; - } - - public function getPlainPassword(): ?string + public function getAccounting(): ?Accounting { - return $this->plainPassword; + return $this->accounting; } - public function setPlainPassword(string $plainPassword): static + public function setAccounting(?Accounting $accounting): static { - $this->plainPassword = $plainPassword; + $this->accounting = $accounting; return $this; } /** - * @see UserInterface + * @return Collection */ - public function eraseCredentials(): void - { - // If you store any temporary, sensitive data on the user, clear it here - $this->plainPassword = null; - } - - public function getEmail(): ?string - { - return $this->email; - } - - public function setEmail(string $email): static - { - $this->email = $email; - - return $this; - } - - public function isEmailConfirmed(): ?bool + public function getProjects(): Collection { - return $this->emailConfirmed; + return $this->projects; } - public function setEmailConfirmed(bool $emailConfirmed): static + public function addProject(Project $project): static { - $this->emailConfirmed = $emailConfirmed; + if (!$this->projects->contains($project)) { + $this->projects->add($project); + $project->setOwner($this); + } return $this; } - public function getAccounting(): ?Accounting - { - return $this->accounting; - } - - public function setAccounting(?Accounting $accounting): static + public function removeProject(Project $project): static { - $this->accounting = $accounting; + if ($this->projects->removeElement($project)) { + // set the owning side to null (unless already changed) + if ($project->getOwner() === $this) { + $project->setOwner(null); + } + } return $this; } @@ -357,50 +303,40 @@ public function setName(?string $name): static return $this; } - /** - * @return Collection - */ - public function getProjects(): Collection + public function getPersonalData(): ?UserPersonal { - return $this->projects; + return $this->personalData; } - public function addProject(Project $project): static + public function setPersonalData(UserPersonal $personalData): static { - if (!$this->projects->contains($project)) { - $this->projects->add($project); - $project->setOwner($this); + // set the owning side of the relation if necessary + if ($personalData->getUser() !== $this) { + $personalData->setUser($this); } - return $this; - } - - public function removeProject(Project $project): static - { - if ($this->projects->removeElement($project)) { - // set the owning side to null (unless already changed) - if ($project->getOwner() === $this) { - $project->setOwner(null); - } - } + $this->personalData = $personalData; return $this; } - public function getPersonalData(): ?UserPersonal + /** + * @see PasswordAuthenticatedUserInterface + */ + public function getPassword(): string { - return $this->personalData; + return $this->password; } - public function setPersonalData(UserPersonal $personalData): static + public function setPassword(string $password): static { - // set the owning side of the relation if necessary - if ($personalData->getUser() !== $this) { - $personalData->setUser($this); - } - - $this->personalData = $personalData; + $this->password = $password; return $this; } + + public function eraseCredentials(): void + { + // If you store any temporary, sensitive data on the user, clear it here + } } diff --git a/src/Entity/UserPersonal.php b/src/Entity/User/UserPersonal.php similarity index 96% rename from src/Entity/UserPersonal.php rename to src/Entity/User/UserPersonal.php index 13e86fca..6417864e 100644 --- a/src/Entity/UserPersonal.php +++ b/src/Entity/User/UserPersonal.php @@ -1,10 +1,10 @@ setExchangeRate(self::ISO_4217, $rate['currency'], $rate['rate']); } - $this->date = $this->getDataUpdatedDate($data['@attributes']['time']); + $this->date = $this->parseECBTime($data['@attributes']['time']); $this->provider = new BaseCurrencyProvider($provider, self::ISO_4217); $this->converter = new CurrencyConverter($this->provider); } - private function getDataLatest(): array + public function getName(): string { - $data = \simplexml_load_file(self::ECB_DATA); - if (!$data) { - throw new \Exception('Could not retrieve XML data'); - } - - return \json_decode(\json_encode($data), true)['Cube']['Cube']; + return self::NAME; } - private function getDataCached(): array + public function getWeight(): int { - $data = $this->cache->get($this->getName(), function (ItemInterface $item): array { - $item->expiresAfter(self::ECB_DATA_TTL); + return self::WEIGHT; + } - return $this->getDataLatest(); - }); + public function convert(Money $money, string $toCurrency): Money + { + $converted = $this->converter->convert( + MoneyService::toBrick($money), + $toCurrency, + null, + RoundingMode::HALF_EVEN + ); - if (!$data) { - throw new \Exception('Could not retrieve cached data'); - } + return new Money( + $converted->getMinorAmount()->toInt(), + $converted->getCurrency()->getCurrencyCode() + ); + } - return $data; + public function getConversionRate(string $fromCurrency, string $toCurrency): float + { + return $this->provider->getExchangeRate($fromCurrency, $toCurrency)->toFloat(); } - private function getDataUpdatedDate(string $time): \DateTimeInterface + public function getConversionDate(string $fromCurrency, string $toCurrency): \DateTimeInterface { - return \DateTime::createFromFormat( - \DateTimeInterface::RFC3339, - sprintf('%sT16:00:00+01:00', $time), - new \DateTimeZone(self::ECB_TIMEZONE) - ); + $this->provider->getExchangeRate($fromCurrency, $toCurrency); + + return $this->date; } public function getData(): array { try { $cachedData = $this->getDataCached(); + $cachedDate = $this->parseECBTime($cachedData['@attributes']['time']); $currentDate = new \DateTime('now', new \DateTimeZone(self::ECB_TIMEZONE)); $currentDayAt16 = (new \DateTime('now', new \DateTimeZone(self::ECB_TIMEZONE)))->setTime(16, 0); - $cachedDate = $this->getDataUpdatedDate($cachedData['@attributes']['time']); if ( $currentDate > $currentDayAt16 && $cachedDate < $currentDayAt16 ) { - $this->cache->delete($this->getName()); + $this->cache->delete(self::NAME); $cachedData = $this->getDataCached(); } @@ -115,40 +121,37 @@ public function getData(): array } } - public function getName(): string - { - return 'european_central_bank'; - } - - public function getWeight(): int + private function parseECBTime(string $time): \DateTimeInterface { - return 100; + return \DateTime::createFromFormat( + \DateTimeInterface::RFC3339, + \sprintf('%sT16:00:00+01:00', $time), + new \DateTimeZone(self::ECB_TIMEZONE) + ); } - public function convert(MoneyContainer $money, string $toCurrency): EntityMoney + private function getDataLatest(): array { - $converted = $this->converter->convert( - $money, - $toCurrency, - null, - RoundingMode::HALF_EVEN - ); + $data = \simplexml_load_file(self::ECB_DATA); + if (!$data) { + throw new \Exception('Could not retrieve XML data'); + } - return new EntityMoney( - $converted->getMinorAmount()->toInt(), - $converted->getCurrency()->getCurrencyCode() - ); + return \json_decode(\json_encode($data), true)['Cube']['Cube']; } - public function getConversionRate(string $fromCurrency, string $toCurrency): float + private function getDataCached(): array { - return $this->provider->getExchangeRate($fromCurrency, $toCurrency)->toFloat(); - } + $data = $this->cache->get(self::NAME, function (ItemInterface $item): array { + $item->expiresAfter(self::ECB_DATA_TTL); - public function getConversionDate(string $fromCurrency, string $toCurrency): \DateTimeInterface - { - $this->provider->getExchangeRate($fromCurrency, $toCurrency); + return $this->getDataLatest(); + }); - return $this->date; + if (!$data) { + throw new \Exception('Could not retrieve cached data'); + } + + return $data; } } diff --git a/src/Library/Economy/Currency/ExchangeInterface.php b/src/Library/Economy/Currency/ExchangeInterface.php index d2e2f175..a4c2aee5 100644 --- a/src/Library/Economy/Currency/ExchangeInterface.php +++ b/src/Library/Economy/Currency/ExchangeInterface.php @@ -4,7 +4,6 @@ use App\Entity\Money; use Brick\Money\Exception\CurrencyConversionException; -use Brick\Money\MoneyContainer; interface ExchangeInterface { @@ -19,14 +18,14 @@ public function getName(): string; public function getWeight(): int; /** - * @param MoneyContainer $money The money to be converted - * @param string $toCurrency The currency to convert to + * @param Money $money The money to be converted + * @param string $toCurrency The currency to convert to * * @return Money The converted Money * * @throws CurrencyConversionException If the exchange rate is not available */ - public function convert(MoneyContainer $money, string $toCurrency): Money; + public function convert(Money $money, string $toCurrency): Money; /** * @param string $fromCurrency The currency to convert from diff --git a/src/Library/Economy/Currency/ExchangeLocator.php b/src/Library/Economy/Currency/ExchangeLocator.php index f1250714..00a715fc 100644 --- a/src/Library/Economy/Currency/ExchangeLocator.php +++ b/src/Library/Economy/Currency/ExchangeLocator.php @@ -7,7 +7,7 @@ class ExchangeLocator { /** - * @var ExchangeInterface[] + * @var array */ private array $exchanges; @@ -33,9 +33,9 @@ public function __construct(iterable $exchanges) } /** - * @return ExchangeInterface[] + * @return array */ - public function getExchanges(): array + public function getAll(): array { return $this->exchanges; } @@ -45,10 +45,10 @@ public function getExchanges(): array * * @throws \Exception When the $name does not match to that of an implemented Exchange */ - public function getExchange(string $name): ExchangeInterface + public function getByName(string $name): ExchangeInterface { if (!\array_key_exists($name, $this->exchanges)) { - throw new \Exception("No such Exchange with the name $name"); + throw new \Exception("No such Exchange with the name '$name'"); } return $this->exchanges[$name]; @@ -60,7 +60,7 @@ public function getExchange(string $name): ExchangeInterface * * @throws CurrencyConversionException If the exchange rate is not available */ - public function getExchangeFor(string $source, string $target): ExchangeInterface + public function get(string $source, string $target): ExchangeInterface { foreach ($this->exchanges as $exchange) { try { diff --git a/src/Library/Economy/MoneyService.php b/src/Library/Economy/MoneyService.php index c6649fca..3b0a17fe 100644 --- a/src/Library/Economy/MoneyService.php +++ b/src/Library/Economy/MoneyService.php @@ -78,8 +78,8 @@ private function convert(Money $money, string $toCurrency): Brick\Money return self::toBrick($money); } - $exchange = $this->exchangeLocator->getExchangeFor($fromCurrency, $toCurrency); + $exchange = $this->exchangeLocator->get($fromCurrency, $toCurrency); - return self::toBrick($exchange->convert(self::toBrick($money), $toCurrency)); + return self::toBrick($exchange->convert($money, $toCurrency)); } } diff --git a/src/Mapping/Accounting/AccountingMapper.php b/src/Mapping/Accounting/AccountingMapper.php deleted file mode 100644 index 14e52713..00000000 --- a/src/Mapping/Accounting/AccountingMapper.php +++ /dev/null @@ -1,87 +0,0 @@ -getOwner(); - - $resource = new Resource\Accounting(); - $resource->id = $entity->getId(); - $resource->currency = $entity->getCurrency(); - - $resource->owner = new EmbeddedResource(); - $resource->owner->id = $owner->getId(); - $resource->owner->iri = $this->iriConverter->getIriFromResource($owner); - $resource->owner->resource = $owner; - - $resource->balance = $this->getBalance($owner); - - return $resource; - } - - public function toEntity(Resource\Accounting $resource): Entity\Accounting - { - $entity = new Entity\Accounting(); - - if ($resource->id !== null) { - $entity = $this->accountingRepository->find($resource->id); - } - - $entity->setCurrency($resource->currency); - - /** @var AccountingOwnerInterface */ - $owner = $resource->owner->resource; - $entity->setOwner($owner); - - return $entity; - } - - private function getBalance(AccountingOwnerInterface $owner): Money - { - if ($owner instanceof User) { - return $this->wallet->getBalance($owner->getAccounting()); - } - - $accounting = $owner->getAccounting(); - - $balance = new Money(0, $accounting->getCurrency()); - $transactions = $this->transactionRepository->findByAccounting($accounting); - - foreach ($transactions as $transaction) { - if ($transaction->getTarget() === $accounting) { - $balance = $this->money->add($transaction->getMoney(), $balance); - } - - if ($transaction->getOrigin() === $accounting) { - $balance = $this->money->substract($transaction->getMoney(), $balance); - } - } - - return $balance; - } -} diff --git a/src/Mapping/AutoMapper.php b/src/Mapping/AutoMapper.php new file mode 100644 index 00000000..42bcf0b6 --- /dev/null +++ b/src/Mapping/AutoMapper.php @@ -0,0 +1,24 @@ +innerMapper = InnerMapper::create( + providers: $mapProviders + ); + } + + public function map(array|object $source, string|array|object $target, array $context = []): array|object|null + { + return $this->innerMapper->map($source, $target, $context); + } +} diff --git a/src/Mapping/Gateway/ChargeMapper.php b/src/Mapping/Gateway/ChargeMapper.php deleted file mode 100644 index d74fc630..00000000 --- a/src/Mapping/Gateway/ChargeMapper.php +++ /dev/null @@ -1,46 +0,0 @@ -id = $entity->getId(); - $resource->type = $entity->getType(); - $resource->title = $entity->getTitle(); - $resource->description = $entity->getDescription(); - $resource->money = $entity->getMoney(); - $resource->target = $this->accountingMapper->toResource($entity->getTarget()); - - return $resource; - } - - public function toEntity(Resource\Charge $resource): Entity\Charge - { - $entity = new Entity\Charge(); - - if ($resource->id !== null) { - $entity = $this->chargeRepository->find($resource->id); - } - - $entity->setType($resource->type); - $entity->setTitle($resource->title); - $entity->setDescription($resource->description); - $entity->setMoney($resource->money); - $entity->setTarget($this->accountingMapper->toEntity($resource->target)); - - return $entity; - } -} diff --git a/src/Mapping/Gateway/CheckoutMapper.php b/src/Mapping/Gateway/CheckoutMapper.php deleted file mode 100644 index e8725824..00000000 --- a/src/Mapping/Gateway/CheckoutMapper.php +++ /dev/null @@ -1,69 +0,0 @@ -id = $entity->getId(); - - $gateway = $this->gatewayLocator->getForCheckout($entity); - $resource->gateway = $this->gatewayMapper->toResource($gateway); - - $resource->origin = $this->accountingMapper->toResource($entity->getOrigin()); - - $charges = []; - foreach ($entity->getCharges() as $charge) { - $charges[] = $this->chargeMapper->toResource($charge); - } - - $resource->charges = $charges; - - $resource->status = $entity->getStatus(); - $resource->links = $entity->getLinks(); - $resource->trackings = $entity->getTrackings(); - - return $resource; - } - - public function toEntity(Resource\Checkout $resource): Entity\Checkout - { - $entity = new Entity\Checkout(); - - if ($resource->id !== null) { - $entity = $this->checkoutRepository->find($resource->id); - } - - $entity->setGatewayName($resource->gateway->name); - $entity->setOrigin($this->accountingMapper->toEntity($resource->origin)); - - $charges = []; - foreach ($resource->charges as $charge) { - $charges[] = $this->chargeMapper->toEntity($charge); - } - - $entity->setCharges(new ArrayCollection($charges)); - - $entity->setStatus($resource->status); - $entity->setLinks($resource->links); - - return $entity; - } -} diff --git a/src/Mapping/Gateway/GatewayMapper.php b/src/Mapping/Gateway/GatewayMapper.php deleted file mode 100644 index 16e06a76..00000000 --- a/src/Mapping/Gateway/GatewayMapper.php +++ /dev/null @@ -1,18 +0,0 @@ -name = $gateway::getName(); - $resource->supports = $gateway::getSupportedChargeTypes(); - - return $resource; - } -} diff --git a/src/Repository/ProjectRepository.php b/src/Repository/Project/ProjectRepository.php similarity index 96% rename from src/Repository/ProjectRepository.php rename to src/Repository/Project/ProjectRepository.php index ffd8d5e0..704f9f9a 100644 --- a/src/Repository/ProjectRepository.php +++ b/src/Repository/Project/ProjectRepository.php @@ -1,9 +1,9 @@ + */ +class RewardClaimRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, RewardClaim::class); + } + + // /** + // * @return RewardClaim[] Returns an array of RewardClaim 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): ?RewardClaim + // { + // return $this->createQueryBuilder('r') + // ->andWhere('r.exampleField = :val') + // ->setParameter('val', $value) + // ->getQuery() + // ->getOneOrNullResult() + // ; + // } +} diff --git a/src/Repository/Project/RewardRepository.php b/src/Repository/Project/RewardRepository.php new file mode 100644 index 00000000..2bbd2964 --- /dev/null +++ b/src/Repository/Project/RewardRepository.php @@ -0,0 +1,43 @@ + + */ +class RewardRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Reward::class); + } + + // /** + // * @return Reward[] Returns an array of Reward objects + // */ + // public function findByExampleField($value): array + // { + // return $this->createQueryBuilder('p') + // ->andWhere('p.exampleField = :val') + // ->setParameter('val', $value) + // ->orderBy('p.id', 'ASC') + // ->setMaxResults(10) + // ->getQuery() + // ->getResult() + // ; + // } + + // public function findOneBySomeField($value): ?Reward + // { + // return $this->createQueryBuilder('p') + // ->andWhere('p.exampleField = :val') + // ->setParameter('val', $value) + // ->getQuery() + // ->getOneOrNullResult() + // ; + // } +} diff --git a/src/Repository/UserPersonalRepository.php b/src/Repository/User/UserPersonalRepository.php similarity index 91% rename from src/Repository/UserPersonalRepository.php rename to src/Repository/User/UserPersonalRepository.php index e4036e74..0bf629aa 100644 --- a/src/Repository/UserPersonalRepository.php +++ b/src/Repository/User/UserPersonalRepository.php @@ -1,8 +1,8 @@ getOwner(); + + if ($owner instanceof User) { + return $this->wallet->getBalance($accounting); + } + + $balance = new Money(0, $accounting->getCurrency()); + $transactions = $this->transactionRepository->findByAccounting($accounting); + + foreach ($transactions as $transaction) { + if ($transaction->getTarget() === $accounting) { + $balance = $this->money->add($transaction->getMoney(), $balance); + } + + if ($transaction->getOrigin() === $accounting) { + $balance = $this->money->substract($transaction->getMoney(), $balance); + } + } + + return $balance; + } +} diff --git a/src/Service/Auth/AuthService.php b/src/Service/Auth/AuthService.php index d9e7a402..5725156c 100644 --- a/src/Service/Auth/AuthService.php +++ b/src/Service/Auth/AuthService.php @@ -2,8 +2,8 @@ namespace App\Service\Auth; -use App\Entity\User; -use App\Entity\UserToken; +use App\Entity\User\User; +use App\Entity\User\UserToken; class AuthService { diff --git a/src/State/Accounting/AccountingStateProcessor.php b/src/State/Accounting/AccountingStateProcessor.php index 12fc1078..57375fb4 100644 --- a/src/State/Accounting/AccountingStateProcessor.php +++ b/src/State/Accounting/AccountingStateProcessor.php @@ -5,31 +5,30 @@ use ApiPlatform\Doctrine\Common\State\PersistProcessor; use ApiPlatform\Metadata\Operation; use ApiPlatform\State\ProcessorInterface; -use App\ApiResource\Accounting as ApiResource; -use App\Entity\Accounting as Entity; -use App\Mapping\Accounting\AccountingMapper; +use App\ApiResource\Accounting\AccountingApiResource; +use App\Entity\Accounting\Accounting; +use App\Mapping\AutoMapper; use Symfony\Component\DependencyInjection\Attribute\Autowire; class AccountingStateProcessor implements ProcessorInterface { public function __construct( - private AccountingMapper $accountingMapper, #[Autowire(service: PersistProcessor::class)] private ProcessorInterface $persistProcessor, + private AutoMapper $autoMapper, ) {} /** - * @param ApiResource\Accounting $data + * @param AccountingApiResource * - * @return ApiResource\Accounting + * @return Accounting */ public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []) { - /** @var Entity\Accounting */ - $entity = $this->accountingMapper->toEntity($data); + /** @var Accounting */ + $entity = $this->autoMapper->map($data, Accounting::class); + $entity = $this->persistProcessor->process($entity, $operation, $uriVariables, $context); - $this->persistProcessor->process($entity, $operation, $uriVariables, $context); - - return $this->accountingMapper->toResource($entity); + return $this->autoMapper->map($entity, AccountingApiResource::class); } } diff --git a/src/State/Accounting/AccountingStateProvider.php b/src/State/Accounting/AccountingStateProvider.php index 3982a52a..3ef5d91f 100644 --- a/src/State/Accounting/AccountingStateProvider.php +++ b/src/State/Accounting/AccountingStateProvider.php @@ -2,15 +2,22 @@ namespace App\State\Accounting; +use ApiPlatform\Api\IriConverterInterface; use ApiPlatform\Doctrine\Orm\State\CollectionProvider; use ApiPlatform\Doctrine\Orm\State\ItemProvider; use ApiPlatform\Metadata\CollectionOperationInterface; use ApiPlatform\Metadata\Operation; use ApiPlatform\State\Pagination\TraversablePaginator; use ApiPlatform\State\ProviderInterface; -use App\ApiResource\Accounting as ApiResource; -use App\Entity\Accounting as Entity; -use App\Mapping\Accounting\AccountingMapper; +use App\ApiResource\Accounting\AccountingApiResource; +use App\ApiResource\Project\ProjectApiResource; +use App\ApiResource\User\UserApiResource; +use App\Entity\Accounting\Accounting; +use App\Entity\Project\Project; +use App\Entity\Tipjar; +use App\Entity\User\User; +use App\Mapping\AutoMapper; +use App\Service\AccountingService; use Symfony\Component\DependencyInjection\Attribute\Autowire; class AccountingStateProvider implements ProviderInterface @@ -20,7 +27,9 @@ public function __construct( private ProviderInterface $itemProvider, #[Autowire(service: CollectionProvider::class)] private ProviderInterface $collectionProvider, - private AccountingMapper $accountingMapper, + private AutoMapper $autoMapper, + private AccountingService $accountingService, + private IriConverterInterface $iriConverter, ) {} public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null @@ -43,15 +52,34 @@ public function provide(Operation $operation, array $uriVariables = [], array $c $accounting = $this->itemProvider->provide($operation, $uriVariables, $context); + if ($accounting === null) { + return null; + } + return $this->toResource($accounting); } - private function toResource(?Entity\Accounting $accounting): ?ApiResource\Accounting + private function toResource(Accounting $accounting): AccountingApiResource { - if ($accounting === null) { - return null; + /** @var AccountingApiResource */ + $resource = $this->autoMapper->map($accounting, AccountingApiResource::class); + $resource->balance = $this->accountingService->calcBalance($accounting); + + $owner = $accounting->getOwner(); + + switch ($owner::class) { + case User::class: + $resourceClass = UserApiResource::class; + break; + case Project::class: + $resourceClass = ProjectApiResource::class; + break; + case Tipjar::class: + $resourceClass = Tipjar::class; } - return $this->accountingMapper->toResource($accounting); + $resource->owner = $this->autoMapper->map($owner, $resourceClass); + + return $resource; } } diff --git a/src/State/ApiResourceStateProcessor.php b/src/State/ApiResourceStateProcessor.php new file mode 100644 index 00000000..8bbfbb5d --- /dev/null +++ b/src/State/ApiResourceStateProcessor.php @@ -0,0 +1,49 @@ +getEntity($data, $operation->getStateOptions()); + + if ($operation instanceof DeleteOperationInterface) { + $this->deleteProcessor->process($entity, $operation, $uriVariables, $context); + + return null; + } + + $this->persistProcessor->process($entity, $operation, $uriVariables, $context); + + return $this->autoMapper->map($entity, $data); + } + + public function getEntity(mixed $data, Options $options): object + { + /** @var object */ + $entity = $this->autoMapper->map($data, $options->getEntityClass(), ['skip_null_values' => true]); + + return $entity; + } +} diff --git a/src/State/ApiResourceStateProvider.php b/src/State/ApiResourceStateProvider.php new file mode 100644 index 00000000..9b41b71c --- /dev/null +++ b/src/State/ApiResourceStateProvider.php @@ -0,0 +1,52 @@ +getClass(); + + if ($operation instanceof CollectionOperationInterface) { + $collection = $this->collectionProvider->provide($operation, $uriVariables, $context); + + $resources = []; + foreach ($collection as $item) { + $resources[] = $this->autoMapper->map($item, $resourceClass); + } + + return new TraversablePaginator( + new \ArrayIterator($resources), + $collection->getCurrentPage(), + $collection->getItemsPerPage(), + $collection->getTotalItems() + ); + } + + $item = $this->itemProvider->provide($operation, $uriVariables, $context); + + if (!$item) { + return null; + } + + return $this->autoMapper->map($item, $resourceClass); + } +} diff --git a/src/State/Gateway/ChargeStateProvider.php b/src/State/Gateway/ChargeStateProvider.php deleted file mode 100644 index 5aad8f4b..00000000 --- a/src/State/Gateway/ChargeStateProvider.php +++ /dev/null @@ -1,29 +0,0 @@ -itemProvider->provide($operation, $uriVariables, $context); - - if ($charge === null) { - return null; - } - - return $this->chargeMapper->toResource($charge); - } -} diff --git a/src/State/Gateway/CheckoutStateProcessor.php b/src/State/Gateway/CheckoutStateProcessor.php index b11ae0cc..a9f092bd 100644 --- a/src/State/Gateway/CheckoutStateProcessor.php +++ b/src/State/Gateway/CheckoutStateProcessor.php @@ -5,33 +5,34 @@ use ApiPlatform\Doctrine\Common\State\PersistProcessor; use ApiPlatform\Metadata\Operation; use ApiPlatform\State\ProcessorInterface; -use App\ApiResource\Gateway as Resource; +use App\ApiResource\Gateway\CheckoutApiResource; +use App\Entity\Gateway\Checkout; use App\Gateway\GatewayLocator; -use App\Mapping\Gateway\CheckoutMapper; +use App\Mapping\AutoMapper; use Symfony\Component\DependencyInjection\Attribute\Autowire; class CheckoutStateProcessor implements ProcessorInterface { public function __construct( - private CheckoutMapper $checkoutMapper, #[Autowire(service: PersistProcessor::class)] private ProcessorInterface $innerProcessor, private GatewayLocator $gatewayLocator, + private AutoMapper $autoMapper, ) {} /** - * @param Resource\Checkout $data + * @param CheckoutApiResource $data * - * @return Resource\Checkout + * @return Checkout */ public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []) { - $entity = $this->checkoutMapper->toEntity($data); + $entity = $this->autoMapper->map($data, Checkout::class); $entity = $this->innerProcessor->process($entity, $operation, $uriVariables, $context); $entity = $this->gatewayLocator->get($data->gateway->name)->process($entity); $entity = $this->innerProcessor->process($entity, $operation, $uriVariables, $context); - return $this->checkoutMapper->toResource($entity); + return $this->autoMapper->map($entity, CheckoutApiResource::class); } } diff --git a/src/State/Gateway/CheckoutStateProvider.php b/src/State/Gateway/CheckoutStateProvider.php deleted file mode 100644 index 0d2d3b96..00000000 --- a/src/State/Gateway/CheckoutStateProvider.php +++ /dev/null @@ -1,44 +0,0 @@ -collectionProvider->provide($operation, $uriVariables, $context); - - $resources = []; - foreach ($entities as $entity) { - $resources[] = $this->checkoutMapper->toResource($entity); - } - - return $resources; - } - - $entity = $this->itemProvider->provide($operation, $uriVariables, $context); - - if ($entity === null) { - return null; - } - - return $this->checkoutMapper->toResource($entity); - } -} diff --git a/src/State/Gateway/GatewayStateProvider.php b/src/State/Gateway/GatewayStateProvider.php index 4c3e477e..3397d2a7 100644 --- a/src/State/Gateway/GatewayStateProvider.php +++ b/src/State/Gateway/GatewayStateProvider.php @@ -5,16 +5,15 @@ use ApiPlatform\Metadata as API; use ApiPlatform\Metadata\Operation; use ApiPlatform\State\ProviderInterface; -use App\ApiResource\Gateway\Gateway; +use App\ApiResource\Gateway\GatewayApiResource; +use App\Gateway\GatewayInterface; use App\Gateway\GatewayLocator; -use App\Mapping\Gateway\GatewayMapper; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; class GatewayStateProvider implements ProviderInterface { public function __construct( private GatewayLocator $gateways, - private GatewayMapper $gatewayMapper, ) {} public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null @@ -31,20 +30,29 @@ private function getGateways(): array { $gateways = []; foreach ($this->gateways->getAll() as $gateway) { - $gateways[] = $this->gatewayMapper->toResource($gateway); + $gateways[] = $this->toResource($gateway); } return $gateways; } - private function getGateway(string $name): Gateway + private function getGateway(string $name): GatewayApiResource { try { $gateway = $this->gateways->get($name); - return $this->gatewayMapper->toResource($gateway); + return $this->toResource($gateway); } catch (\Exception $e) { throw new NotFoundHttpException('Not Found'); } } + + private function toResource(GatewayInterface $gateway): GatewayApiResource + { + $resource = new GatewayApiResource(); + $resource->name = $gateway::getName(); + $resource->supports = $gateway::getSupportedChargeTypes(); + + return $resource; + } } diff --git a/src/State/Project/ProjectStateProcessor.php b/src/State/Project/ProjectStateProcessor.php new file mode 100644 index 00000000..0934a4e9 --- /dev/null +++ b/src/State/Project/ProjectStateProcessor.php @@ -0,0 +1,56 @@ +autoMapper->map($data, Project::class, ['skip_null_values' => true]); + + if (!isset($data->id)) { + $user = $this->security->getUser(); + $owner = $this->userRepository->findOneBy(['username' => $user->getUserIdentifier()]); + + $project->setOwner($owner); + } + + if ($operation instanceof DeleteOperationInterface) { + $this->deleteProcessor->process($project, $operation, $uriVariables, $context); + + return null; + } + + $this->persistProcessor->process($project, $operation, $uriVariables, $context); + + return $this->autoMapper->map($project, $data); + } +} diff --git a/src/State/UserStateProcessor.php b/src/State/User/UserStateProcessor.php similarity index 50% rename from src/State/UserStateProcessor.php rename to src/State/User/UserStateProcessor.php index f9d1452d..968063c9 100644 --- a/src/State/UserStateProcessor.php +++ b/src/State/User/UserStateProcessor.php @@ -1,32 +1,28 @@ getPlainPassword()) { - $data->setPassword( - $this->userPasswordHasher->hashPassword($data, $data->getPlainPassword()) - ); - } - - return $this->innerProcessor->process($data, $operation, $uriVariables, $context); + return $data; } } diff --git a/src/State/UserTokenLoginProcessor.php b/src/State/User/UserTokenLoginProcessor.php similarity index 80% rename from src/State/UserTokenLoginProcessor.php rename to src/State/User/UserTokenLoginProcessor.php index b3aa54b3..85a285a5 100644 --- a/src/State/UserTokenLoginProcessor.php +++ b/src/State/User/UserTokenLoginProcessor.php @@ -1,12 +1,13 @@ userRepository->findOneByIdentifier($data->identifier); @@ -43,6 +47,6 @@ public function process(mixed $data, Operation $operation, array $uriVariables = $this->entityManager->persist($token); $this->entityManager->flush(); - return $token; + return $this->autoMapper->map($token, UserTokenApiResource::class); } } diff --git a/symfony.lock b/symfony.lock index d601f29e..28dbadba 100644 --- a/symfony.lock +++ b/symfony.lock @@ -145,6 +145,18 @@ "src/Kernel.php" ] }, + "symfony/lock": { + "version": "6.4", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "5.2", + "ref": "8e937ff2b4735d110af1770f242c1107fdab4c8e" + }, + "files": [ + "config/packages/lock.yaml" + ] + }, "symfony/maker-bundle": { "version": "1.51", "recipe": { diff --git a/tests/Entity/ProjectApiTest.php b/tests/Entity/ProjectApiTest.php index ab1978f3..278ad273 100644 --- a/tests/Entity/ProjectApiTest.php +++ b/tests/Entity/ProjectApiTest.php @@ -3,9 +3,9 @@ namespace App\Tests\Entity; use ApiPlatform\Symfony\Bundle\Test\ApiTestCase; -use App\Entity\Project; -use App\Entity\ProjectStatus; -use App\Entity\User; +use App\Entity\Project\Project; +use App\Entity\Project\ProjectStatus; +use App\Entity\User\User; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\HttpFoundation\Response; use Zenstruck\Foundry\Test\ResetDatabase; @@ -41,7 +41,7 @@ public function testGetCollection(): void $project = new Project(); $project->setTitle('Test Project'); $project->setOwner($owner); - $project->setStatus(ProjectStatus::Editing); + $project->setStatus(ProjectStatus::InEditing); $this->entityManager->persist($owner); $this->entityManager->persist($project); @@ -54,8 +54,8 @@ public function testGetCollection(): void $this->assertJsonContains(['hydra:member' => [ [ 'title' => 'Test Project', - 'accounting' => [], - 'status' => ProjectStatus::Editing->value, + 'status' => ProjectStatus::InEditing->value, + 'rewards' => [], ], ]]); } diff --git a/tests/Gateway/Wallet/WalletServiceTest.php b/tests/Gateway/Wallet/WalletServiceTest.php index 72255bc1..daa91267 100644 --- a/tests/Gateway/Wallet/WalletServiceTest.php +++ b/tests/Gateway/Wallet/WalletServiceTest.php @@ -5,7 +5,7 @@ use App\Entity\Accounting\Transaction; use App\Entity\Money; use App\Entity\Tipjar; -use App\Entity\User; +use App\Entity\User\User; use App\Gateway\Wallet\StatementDirection; use App\Gateway\Wallet\WalletService; use Doctrine\ORM\EntityManagerInterface; diff --git a/tests/Library/Benzina/Pump/UsersPumpTest.php b/tests/Library/Benzina/Pump/UsersPumpTest.php index b4afbb85..9489ef2d 100644 --- a/tests/Library/Benzina/Pump/UsersPumpTest.php +++ b/tests/Library/Benzina/Pump/UsersPumpTest.php @@ -2,7 +2,7 @@ namespace App\Tests\Library\Benzina\Pump; -use App\Entity\User; +use App\Entity\User\User; use App\Library\Benzina\Pump\UsersPump; use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;