Merge pull request #99 from goteo/feat/project-reward-claim-validation

[feat] Project RewardClaim validation
This commit is contained in:
2025-04-03 13:24:56 +02:00
committed by GitHub
9 changed files with 128 additions and 44 deletions
@@ -4,10 +4,13 @@ namespace App\ApiResource\Project;
use ApiPlatform\Doctrine\Orm\State\Options;
use ApiPlatform\Metadata as API;
use App\ApiResource\Gateway\ChargeApiResource;
use App\ApiResource\User\UserApiResource;
use App\Entity\Project\RewardClaim;
use App\State\ApiResourceStateProvider;
use App\State\Project\RewardClaimStateProcessor;
use App\Validator\AvailableRewardUnits;
use App\Validator\EnoughRewardCharge;
use Symfony\Component\Validator\Constraints as Assert;
/**
@@ -19,20 +22,28 @@ use Symfony\Component\Validator\Constraints as Assert;
provider: ApiResourceStateProvider::class,
processor: RewardClaimStateProcessor::class
)]
#[EnoughRewardCharge()]
class RewardClaimApiResource
{
#[API\ApiProperty(identifier: true, writable: false)]
public int $id;
/**
* The ProjectReward being claimed.
*/
#[Assert\NotBlank()]
public RewardApiResource $reward;
/**
* The User claiming the ProjectReward.
*/
#[API\ApiProperty(writable: false)]
public UserApiResource $owner;
/**
* The GatewayCharge granting access to the ProjectReward.
*/
#[Assert\NotBlank()]
public ChargeApiResource $charge;
/**
* The ProjectReward being claimed.
*/
#[Assert\NotBlank()]
#[AvailableRewardUnits()]
public RewardApiResource $reward;
}
+17
View File
@@ -2,6 +2,7 @@
namespace App\Entity\Project;
use App\Entity\Gateway\Charge;
use App\Entity\Interface\UserOwnedInterface;
use App\Entity\Trait\UserOwnedTrait;
use App\Entity\User\User;
@@ -22,6 +23,10 @@ class RewardClaim implements UserOwnedInterface
#[ORM\Column]
private ?int $id = null;
#[ORM\OneToOne(cascade: ['persist', 'remove'])]
#[ORM\JoinColumn(nullable: false)]
private ?Charge $charge = null;
#[ORM\ManyToOne(inversedBy: 'claims')]
#[ORM\JoinColumn(nullable: false)]
private ?Reward $reward = null;
@@ -35,6 +40,18 @@ class RewardClaim implements UserOwnedInterface
return $this->id;
}
public function getCharge(): ?Charge
{
return $this->charge;
}
public function setCharge(Charge $charge): static
{
$this->charge = $charge;
return $this;
}
public function getReward(): ?Reward
{
return $this->reward;
-34
View File
@@ -1,34 +0,0 @@
<?php
namespace App\Service\Project;
use App\Entity\Project\Reward;
use App\Entity\Project\RewardClaim;
class RewardService
{
/**
* Processes a RewardClaim for the set Reward.
*
* @return RewardClaim The processed RewardClaim with updated Reward
*/
public function processClaim(RewardClaim $claim): RewardClaim
{
$reward = $claim->getReward();
if (!$reward->hasUnits()) {
return $claim;
}
$available = $reward->getUnitsAvailable();
if ($available < 1) {
throw new \Exception('The claimed Reward has no units available');
}
$reward->addClaim($claim);
$reward->setUnitsAvailable($available - 1);
return $claim->setReward($reward);
}
}
@@ -8,7 +8,6 @@ use App\ApiResource\Project\RewardClaimApiResource;
use App\Entity\Project\RewardClaim;
use App\Mapping\AutoMapper;
use App\Service\Auth\AuthService;
use App\Service\Project\RewardService;
use App\State\EntityStateProcessor;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
@@ -18,7 +17,6 @@ class RewardClaimStateProcessor implements ProcessorInterface
private EntityStateProcessor $entityStateProcessor,
private AutoMapper $autoMapper,
private AuthService $authService,
private RewardService $rewardService,
) {}
/**
@@ -41,7 +39,6 @@ class RewardClaimStateProcessor implements ProcessorInterface
$claim->setOwner($owner);
}
$claim = $this->rewardService->processClaim($claim);
$claim = $this->entityStateProcessor->process($claim, $operation, $uriVariables, $context);
return $this->autoMapper->map($claim, $data);
+5 -1
View File
@@ -21,9 +21,13 @@ class SupportStateProcessor implements ProcessorInterface
private EntityManagerInterface $entityManager,
) {}
/**
* @param SupportApiResource $data
*
* @return SupportApiResource
*/
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = [])
{
// Verify if the user is authenticated
$user = $this->authService->getUser();
if (!$user) {
throw new AuthenticationException();
+11
View File
@@ -0,0 +1,11 @@
<?php
namespace App\Validator;
use Symfony\Component\Validator\Constraint;
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
final class AvailableRewardUnits extends Constraint
{
public string $message = 'The ProjectReward "{{ id }}" has no units available.';
}
@@ -0,0 +1,29 @@
<?php
namespace App\Validator;
use App\ApiResource\Project\RewardApiResource;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
final class AvailableRewardUnitsValidator extends ConstraintValidator
{
/**
* @param RewardApiResource $value
*/
public function validate(mixed $value, Constraint $constraint): void
{
if (!$value->hasUnits) {
return;
}
if ($value->hasUnits && $value->unitsAvailable > 0) {
return;
}
$this->context->buildViolation($constraint->message)
->setParameter('{{ id }}', $value->id)
->addViolation()
;
}
}
+16
View File
@@ -0,0 +1,16 @@
<?php
namespace App\Validator;
use Symfony\Component\Validator\Constraint;
#[\Attribute(\Attribute::TARGET_CLASS)]
final class EnoughRewardCharge extends Constraint
{
public string $message = 'The charged money is not enough to claim the ProjectReward.';
public function getTargets(): string
{
return self::CLASS_CONSTRAINT;
}
}
@@ -0,0 +1,33 @@
<?php
namespace App\Validator;
use App\ApiResource\Project\RewardClaimApiResource;
use App\Library\Economy\MoneyService;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
final class EnoughRewardChargeValidator extends ConstraintValidator
{
public function __construct(
private MoneyService $money,
) {}
/**
* @param RewardClaimApiResource $value
* @param EnoughRewardCharge $constraint
*/
public function validate(mixed $value, Constraint $constraint): void
{
$charge = $value->charge->money;
$reward = $value->reward->money;
if ($this->money->isMoreOrSame($charge, $reward)) {
return;
}
$this->context->buildViolation($constraint->message)
->addViolation()
;
}
}