Skip to content

Commit

Permalink
Merge pull request #36 from GoteoFoundation/feat/project-rewards
Browse files Browse the repository at this point in the history
[feat] Project Rewards
  • Loading branch information
subiabre authored Dec 11, 2024
2 parents 6c21fc7 + e1133b7 commit e6b32c7
Show file tree
Hide file tree
Showing 16 changed files with 527 additions and 18 deletions.
1 change: 1 addition & 0 deletions config/bundles.php
Original file line number Diff line number Diff line change
Expand Up @@ -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],
];
7 changes: 7 additions & 0 deletions src/ApiResource/Project/ProjectApiResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,11 @@ class ProjectApiResource
#[API\ApiFilter(filterClass: SearchFilter::class, strategy: 'exact')]
#[API\ApiProperty(securityPostDenormalize: 'is_granted("PROJECT_EDIT")')]
public ProjectStatus $status = ProjectStatus::InEditing;

/**
* List of the ProjectRewards this Project offers.
*
* @var array<int, RewardApiResource>
*/
public array $rewards;
}
73 changes: 73 additions & 0 deletions src/ApiResource/Project/RewardApiResource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php

namespace App\ApiResource\Project;

use ApiPlatform\Doctrine\Orm\State\Options;
use ApiPlatform\Metadata as API;
use App\Entity\Money;
use App\Entity\Project\Reward;
use App\State\ApiResourceStateProcessor;
use App\State\ApiResourceStateProvider;
use AutoMapper\Attribute\MapTo;
use Symfony\Component\Validator\Constraints as Assert;

/**
* A ProjectReward is something the Project owner wishes to give in exchange for contributions to their Project.
*/
#[API\ApiResource(
shortName: 'ProjectReward',
stateOptions: new Options(entityClass: Reward::class),
provider: ApiResourceStateProvider::class,
processor: ApiResourceStateProcessor::class
)]
class RewardApiResource
{
#[API\ApiProperty(identifier: true, writable: false)]
public int $id;

/**
* The project which gives this reward.
*/
#[Assert\NotBlank()]
public ProjectApiResource $project;

/**
* A short, descriptive title for this reward.
*/
#[Assert\NotBlank()]
public string $title;

/**
* Detailed information about this reward.
*/
#[MapTo(if: 'source.description != null')]
public ?string $description = null;

/**
* The minimal monetary sum to be able to claim this reward.
*/
#[Assert\NotBlank()]
public Money $money;

/**
* Rewards might be finite, i.e: has a limited amount of existing unitsTotal.
*/
#[Assert\NotNull()]
#[Assert\Type('bool')]
public bool $hasUnits;

/**
* For finite rewards, the total amount of existing unitsTotal.
*/
#[Assert\When(
'this.hasUnits == true',
constraints: [new Assert\Positive()]
)]
public int $unitsTotal = 0;

/**
* For finite rewards, the currently available amount of unitsTotal that can be claimed.
*/
#[API\ApiProperty(writable: false)]
public int $unitsAvailable = 0;
}
38 changes: 38 additions & 0 deletions src/ApiResource/Project/RewardClaimApiResource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

namespace App\ApiResource\Project;

use ApiPlatform\Doctrine\Orm\State\Options;
use ApiPlatform\Metadata as API;
use App\ApiResource\User\UserApiResource;
use App\Entity\Project\RewardClaim;
use App\State\ApiResourceStateProcessor;
use App\State\ApiResourceStateProvider;
use Symfony\Component\Validator\Constraints as Assert;

/**
* A ProjectRewardClaim represents the will of an User who wishes to obtain one ProjectReward.
*/
#[API\ApiResource(
shortName: 'ProjectRewardClaim',
stateOptions: new Options(entityClass: RewardClaim::class),
provider: ApiResourceStateProvider::class,
processor: ApiResourceStateProcessor::class
)]
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;
}
11 changes: 11 additions & 0 deletions src/Entity/Accounting/Accounting.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,17 @@ class Accounting
#[ORM\OneToOne(mappedBy: 'accounting', cascade: ['persist'])]
private ?Tipjar $tipjar = null;

/**
* Create a new Accounting entity instance for the given owner.
*/
public static function of(AccountingOwnerInterface $owner): Accounting
{
$accounting = new Accounting();
$accounting->setOwner($owner);

return $accounting;
}

public function __construct()
{
/*
Expand Down
2 changes: 2 additions & 0 deletions src/Entity/Interface/UserOwnedInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ interface UserOwnedInterface
public function getOwner(): ?User;

public function isOwnedBy(User $user): bool;

public function setOwner(User $user): static;
}
59 changes: 54 additions & 5 deletions src/Entity/Project/Project.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,18 @@

use App\Entity\Accounting\Accounting;
use App\Entity\Interface\AccountingOwnerInterface;
use App\Entity\Interface\UserOwnedInterface;
use App\Entity\Trait\MigratedEntity;
use App\Entity\Trait\TimestampedCreationEntity;
use App\Entity\Trait\TimestampedUpdationEntity;
use App\Entity\User\User;
use App\Repository\Project\ProjectRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity(repositoryClass: ProjectRepository::class)]
class Project implements AccountingOwnerInterface
class Project implements UserOwnedInterface, AccountingOwnerInterface
{
use MigratedEntity;
use TimestampedCreationEntity;
Expand Down Expand Up @@ -50,19 +53,30 @@ class Project implements AccountingOwnerInterface
#[ORM\Column(type: 'string', enumType: ProjectStatus::class)]
private ProjectStatus $status;

/**
* @var Collection<int, Reward>
*/
#[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
{
return $this->id;
}

public function setId(int $id): static
{
$this->id = $id;

return $this;
}

public function getTitle(): ?string
{
return $this->title;
Expand Down Expand Up @@ -92,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;
Expand All @@ -110,4 +129,34 @@ public function setStatus(ProjectStatus $status): static

return $this;
}

/**
* @return Collection<int, Reward>
*/
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;
}
}
Loading

0 comments on commit e6b32c7

Please sign in to comment.