diff --git a/.github/workflows/phpunit.yaml b/.github/workflows/phpunit.yaml index 82b84fe..13bdff3 100644 --- a/.github/workflows/phpunit.yaml +++ b/.github/workflows/phpunit.yaml @@ -10,6 +10,9 @@ jobs: phpunit: runs-on: ubuntu-latest + env: + STRIPE_API_KEY: ${{ secrets.STRIPE_API_KEY }} + services: mariadb: image: mariadb:10.11.2 @@ -25,8 +28,6 @@ jobs: - uses: actions/checkout@v4 - uses: php-actions/composer@v6 - env: - STRIPE_API_KEY: ${{ secrets.STRIPE_API_KEY }} with: php_version: ${{ env.PHP_VERSION }} php_extensions: ${{ env.PHP_EXTENSIONS }} diff --git a/config/services.yaml b/config/services.yaml index 8a6bc90..1ef66fb 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -20,6 +20,8 @@ services: tags: ['app.lib.economy.currency.exchange'] AutoMapper\Provider\ProviderInterface: tags: ['app.mapping.map_provider'] + AutoMapper\Transformer\PropertyTransformer\PropertyTransformerInterface: + tags: ['app.mapping.map_transformer'] # makes classes in src/ available to be used as services # this creates a service per class whose id is the fully-qualified class name @@ -71,6 +73,7 @@ services: arguments: - '%kernel.cache_dir%' - !tagged 'app.mapping.map_provider' + - !tagged 'app.mapping.map_transformer' # add more service definitions when explicit configuration is needed # please note that last definitions always *replace* previous ones diff --git a/src/ApiResource/Accounting/AccountingApiResource.php b/src/ApiResource/Accounting/AccountingApiResource.php index b46ab69..b206dcf 100644 --- a/src/ApiResource/Accounting/AccountingApiResource.php +++ b/src/ApiResource/Accounting/AccountingApiResource.php @@ -11,8 +11,10 @@ use App\Entity\Money; use App\Entity\Project\Project; use App\Entity\Tipjar; use App\Entity\User\User; +use App\Mapping\Transformer\AccountingBalanceMapTransformer; use App\State\Accounting\AccountingStateProcessor; use App\State\Accounting\AccountingStateProvider; +use AutoMapper\Attribute\MapFrom; /** * v4 features an advanced economy model under the hood. @@ -41,18 +43,15 @@ class AccountingApiResource */ public string $currency; + /** + * The money currently held by the Accounting. + */ + #[MapFrom(Accounting::class, transformer: AccountingBalanceMapTransformer::class)] + public Money $balance; + #[API\ApiProperty(readable: false, writable: false)] public string $ownerClass; - #[API\ApiProperty(readable: false, writable: false)] - public ?UserApiResource $user = null; - - #[API\ApiProperty(readable: false, writable: false)] - public ?ProjectApiResource $project = null; - - #[API\ApiProperty(readable: false, writable: false)] - public ?Tipjar $tipjar = null; - /** * The resource owning this Accounting. * @@ -70,8 +69,12 @@ class AccountingApiResource } } - /** - * The money currently held by the Accounting. - */ - public Money $balance; + #[API\ApiProperty(readable: false, writable: false)] + public ?UserApiResource $user = null; + + #[API\ApiProperty(readable: false, writable: false)] + public ?ProjectApiResource $project = null; + + #[API\ApiProperty(readable: false, writable: false)] + public ?Tipjar $tipjar = null; } diff --git a/src/ApiResource/Gateway/CheckoutApiResource.php b/src/ApiResource/Gateway/CheckoutApiResource.php index a1bc838..1ad6b23 100644 --- a/src/ApiResource/Gateway/CheckoutApiResource.php +++ b/src/ApiResource/Gateway/CheckoutApiResource.php @@ -9,9 +9,11 @@ use App\Entity\Gateway\Checkout; use App\Gateway\CheckoutStatus; use App\Gateway\Link; use App\Gateway\Tracking; +use App\Mapping\Transformer\GatewayNameMapTransformer; use App\State\ApiResourceStateProvider; use App\State\Gateway\CheckoutStateProcessor; use AutoMapper\Attribute\MapFrom; +use AutoMapper\Attribute\MapTo; use Symfony\Component\Validator\Constraints as Assert; /** @@ -35,6 +37,8 @@ class CheckoutApiResource * The desired Gateway to checkout with. */ #[Assert\NotBlank()] + #[MapFrom(property: 'gatewayName', transformer: GatewayNameMapTransformer::class)] + #[MapTo(property: 'gatewayName', transformer: 'source.gateway.name')] public GatewayApiResource $gateway; /** diff --git a/src/Entity/Gateway/Charge.php b/src/Entity/Gateway/Charge.php index c7d6165..f7c348e 100644 --- a/src/Entity/Gateway/Charge.php +++ b/src/Entity/Gateway/Charge.php @@ -51,7 +51,7 @@ class Charge * The Accounting receiving the consequent Transaction for this GatewayCharge. */ #[Assert\NotBlank()] - #[ORM\ManyToOne] + #[ORM\ManyToOne(cascade: ['persist'])] #[ORM\JoinColumn(nullable: false)] private ?Accounting $target = null; diff --git a/src/Entity/Gateway/Checkout.php b/src/Entity/Gateway/Checkout.php index 8394c43..bebb762 100644 --- a/src/Entity/Gateway/Checkout.php +++ b/src/Entity/Gateway/Checkout.php @@ -41,7 +41,7 @@ class Checkout * The Accounting that will issue the Transactions of the GatewayCharges after a successful checkout. */ #[Assert\NotBlank()] - #[ORM\ManyToOne] + #[ORM\ManyToOne(cascade: ['persist'])] #[ORM\JoinColumn(nullable: false)] private ?Accounting $origin = null; diff --git a/src/Gateway/Stripe/StripeGateway.php b/src/Gateway/Stripe/StripeGateway.php index dc69356..fe86b00 100644 --- a/src/Gateway/Stripe/StripeGateway.php +++ b/src/Gateway/Stripe/StripeGateway.php @@ -4,7 +4,7 @@ namespace App\Gateway\Stripe; use ApiPlatform\Api\IriConverterInterface; use App\Entity\Gateway\Checkout; -use App\Entity\User; +use App\Entity\User\User; use App\Gateway\ChargeType; use App\Gateway\CheckoutStatus; use App\Gateway\GatewayInterface; @@ -136,7 +136,10 @@ class StripeGateway implements GatewayInterface { $owner = $checkout->getOrigin()->getOwner(); if (!$owner instanceof User) { - return ''; + throw new \Exception(\sprintf( + "Checkout with Stripe must be performed by an User, instance of '%s' supplied", + $owner::class + )); } return $owner->getEmail(); diff --git a/src/Mapping/AutoMapper.php b/src/Mapping/AutoMapper.php index c9c10fc..1e20c33 100644 --- a/src/Mapping/AutoMapper.php +++ b/src/Mapping/AutoMapper.php @@ -18,10 +18,12 @@ class AutoMapper implements AutoMapperInterface public function __construct( ?string $cacheDirectory = null, iterable $mapProviders = [], + iterable $mapTransformers = [], ) { $this->innerMapper = InnerMapper::create( cacheDirectory: \sprintf('%s%s%s', $cacheDirectory, \DIRECTORY_SEPARATOR, self::CACHE_DIR), providers: $mapProviders, + propertyTransformers: $mapTransformers, ); } diff --git a/src/Mapping/Transformer/AccountingBalanceMapTransformer.php b/src/Mapping/Transformer/AccountingBalanceMapTransformer.php new file mode 100644 index 0000000..c829b35 --- /dev/null +++ b/src/Mapping/Transformer/AccountingBalanceMapTransformer.php @@ -0,0 +1,22 @@ +accountingService->calcBalance($source); + } +} diff --git a/src/Mapping/Transformer/GatewayNameMapTransformer.php b/src/Mapping/Transformer/GatewayNameMapTransformer.php new file mode 100644 index 0000000..8f5b132 --- /dev/null +++ b/src/Mapping/Transformer/GatewayNameMapTransformer.php @@ -0,0 +1,25 @@ +gatewayLocator->get($value); + + $resource = new GatewayApiResource(); + $resource->name = $gateway::getName(); + $resource->supports = $gateway::getSupportedChargeTypes(); + + return $resource; + } +} diff --git a/src/State/Accounting/AccountingStateProvider.php b/src/State/Accounting/AccountingStateProvider.php index 3ef5d91..fb4eb11 100644 --- a/src/State/Accounting/AccountingStateProvider.php +++ b/src/State/Accounting/AccountingStateProvider.php @@ -10,12 +10,6 @@ use ApiPlatform\Metadata\Operation; use ApiPlatform\State\Pagination\TraversablePaginator; use ApiPlatform\State\ProviderInterface; 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; @@ -39,7 +33,7 @@ class AccountingStateProvider implements ProviderInterface $resources = []; foreach ($accountings as $accounting) { - $resources[] = $this->toResource($accounting); + $resources[] = $this->autoMapper->map($accounting, AccountingApiResource::class); } return new TraversablePaginator( @@ -56,30 +50,6 @@ class AccountingStateProvider implements ProviderInterface return null; } - return $this->toResource($accounting); - } - - private function toResource(Accounting $accounting): AccountingApiResource - { - /** @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; - } - - $resource->owner = $this->autoMapper->map($owner, $resourceClass); - - return $resource; + return $this->autoMapper->map($accounting, AccountingApiResource::class); } }