From b6f16d417e29e618bf5055db717b43932ae0f9a8 Mon Sep 17 00:00:00 2001 From: Daniel Subiabre Date: Wed, 27 Nov 2024 14:54:05 +0100 Subject: [PATCH 01/36] Add ProjectReward entity --- src/Entity/Project.php | 57 ++++++--- src/Entity/ProjectReward.php | 142 +++++++++++++++++++++ src/Repository/ProjectRewardRepository.php | 43 +++++++ 3 files changed, 224 insertions(+), 18 deletions(-) create mode 100644 src/Entity/ProjectReward.php create mode 100644 src/Repository/ProjectRewardRepository.php diff --git a/src/Entity/Project.php b/src/Entity/Project.php index 1609ad3..3e9ce84 100644 --- a/src/Entity/Project.php +++ b/src/Entity/Project.php @@ -2,29 +2,16 @@ namespace App\Entity; -use ApiPlatform\Doctrine\Orm\Filter\SearchFilter; -use ApiPlatform\Metadata as API; use App\Entity\Accounting\Accounting; use App\Entity\Interface\AccountingOwnerInterface; use App\Entity\Trait\MigratedEntity; use App\Entity\Trait\TimestampedCreationEntity; use App\Entity\Trait\TimestampedUpdationEntity; use App\Repository\ProjectRepository; +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; -/** - * Projects describe a community-led event that is to be discovered, developed and funded by other Users. - */ -#[API\GetCollection()] -#[API\Post(security: 'is_granted("ROLE_USER")')] -#[API\Get()] -#[API\Delete(security: 'is_granted("AUTH_PROJECT_EDIT")')] -#[API\Patch(security: 'is_granted("AUTH_PROJECT_EDIT")')] -#[API\ApiFilter(filterClass: SearchFilter::class, properties: [ - 'title' => 'partial', - 'status' => 'exact', - 'owner' => 'exact', -])] #[ORM\Entity(repositoryClass: ProjectRepository::class)] class Project implements AccountingOwnerInterface { @@ -47,14 +34,12 @@ 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\JoinColumn(nullable: false)] private ?User $owner = null; @@ -63,16 +48,22 @@ 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: ProjectReward::class)] + private Collection $rewards; + public function __construct() { $accounting = new Accounting(); $accounting->setOwner($this); $this->accounting = $accounting; + $this->rewards = new ArrayCollection(); } public function getId(): ?int @@ -127,4 +118,34 @@ public function setStatus(ProjectStatus $status): static return $this; } + + /** + * @return Collection + */ + public function getRewards(): Collection + { + return $this->rewards; + } + + public function addReward(ProjectReward $reward): static + { + if (!$this->rewards->contains($reward)) { + $this->rewards->add($reward); + $reward->setProject($this); + } + + return $this; + } + + public function removeReward(ProjectReward $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/ProjectReward.php b/src/Entity/ProjectReward.php new file mode 100644 index 0000000..ebf4627 --- /dev/null +++ b/src/Entity/ProjectReward.php @@ -0,0 +1,142 @@ +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; + } +} diff --git a/src/Repository/ProjectRewardRepository.php b/src/Repository/ProjectRewardRepository.php new file mode 100644 index 0000000..e4bd634 --- /dev/null +++ b/src/Repository/ProjectRewardRepository.php @@ -0,0 +1,43 @@ + + */ +class ProjectRewardRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, ProjectReward::class); + } + + // /** + // * @return ProjectReward[] Returns an array of ProjectReward 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): ?ProjectReward + // { + // return $this->createQueryBuilder('p') + // ->andWhere('p.exampleField = :val') + // ->setParameter('val', $value) + // ->getQuery() + // ->getOneOrNullResult() + // ; + // } +} From d3e45197a09c390dafba5da11296595301da04ba Mon Sep 17 00:00:00 2001 From: Daniel Subiabre Date: Wed, 27 Nov 2024 14:54:31 +0100 Subject: [PATCH 02/36] Update project statuses --- src/Entity/ProjectStatus.php | 38 ++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/src/Entity/ProjectStatus.php b/src/Entity/ProjectStatus.php index 04c5b81..0111aa3 100644 --- a/src/Entity/ProjectStatus.php +++ b/src/Entity/ProjectStatus.php @@ -2,13 +2,43 @@ namespace App\Entity; +/** + * Projects have a start and an end, and in the meantime they go through different phases represented under this status. + */ enum ProjectStatus: string { + /** + * Project is under edition by it's owner. + */ + case InEditing = 'in_editing'; + + /** + * Owner finished editing and an admin is reviewing it. + */ + case InReview = 'in_review'; + + /** + * An admin reviewed it and rejected it. Final. + */ case Rejected = 'rejected'; - case Editing = 'editing'; - case Reviewing = 'reviewing'; + + /** + * Project was reviewed and is in campaign for funding. + */ case InCampaign = 'in_campaign'; - case Funded = 'funded'; - case Fulfilled = 'fulfilled'; + + /** + * Project finished campaigning but didn't meet funding goals. Final. + */ case Unfunded = 'unfunded'; + + /** + * Project successfully finished campaigning and owner can receive funds. + */ + case InFunding = 'in_funding'; + + /** + * Project owner received funds and fulfilled their goals. Final. + */ + case Fulfilled = 'fulfilled'; } From efa17f6b6451e53c5e760bfed82858bb04d4c124 Mon Sep 17 00:00:00 2001 From: Daniel Subiabre Date: Thu, 28 Nov 2024 12:18:40 +0100 Subject: [PATCH 03/36] Move Project entities inside own namespace --- src/Entity/{ => Project}/Project.php | 15 ++++++++------- src/Entity/{ => Project}/ProjectStatus.php | 2 +- .../{ProjectReward.php => Project/Reward.php} | 9 +++++---- .../{ => Project}/ProjectRepository.php | 4 ++-- .../RewardRepository.php} | 14 +++++++------- 5 files changed, 23 insertions(+), 21 deletions(-) rename src/Entity/{ => Project}/Project.php (90%) rename src/Entity/{ => Project}/ProjectStatus.php (96%) rename src/Entity/{ProjectReward.php => Project/Reward.php} (94%) rename src/Repository/{ => Project}/ProjectRepository.php (96%) rename src/Repository/{ProjectRewardRepository.php => Project/RewardRepository.php} (70%) diff --git a/src/Entity/Project.php b/src/Entity/Project/Project.php similarity index 90% rename from src/Entity/Project.php rename to src/Entity/Project/Project.php index 3e9ce84..cb42bbc 100644 --- a/src/Entity/Project.php +++ b/src/Entity/Project/Project.php @@ -1,13 +1,14 @@ + * @var Collection */ - #[ORM\OneToMany(mappedBy: 'project', targetEntity: ProjectReward::class)] + #[ORM\OneToMany(mappedBy: 'project', targetEntity: Reward::class)] private Collection $rewards; public function __construct() @@ -120,14 +121,14 @@ public function setStatus(ProjectStatus $status): static } /** - * @return Collection + * @return Collection */ public function getRewards(): Collection { return $this->rewards; } - public function addReward(ProjectReward $reward): static + public function addReward(Reward $reward): static { if (!$this->rewards->contains($reward)) { $this->rewards->add($reward); @@ -137,7 +138,7 @@ public function addReward(ProjectReward $reward): static return $this; } - public function removeReward(ProjectReward $reward): static + public function removeReward(Reward $reward): static { if ($this->rewards->removeElement($reward)) { // set the owning side to null (unless already changed) diff --git a/src/Entity/ProjectStatus.php b/src/Entity/Project/ProjectStatus.php similarity index 96% rename from src/Entity/ProjectStatus.php rename to src/Entity/Project/ProjectStatus.php index 0111aa3..366b321 100644 --- a/src/Entity/ProjectStatus.php +++ b/src/Entity/Project/ProjectStatus.php @@ -1,6 +1,6 @@ + * @extends ServiceEntityRepository */ -class ProjectRewardRepository extends ServiceEntityRepository +class RewardRepository extends ServiceEntityRepository { public function __construct(ManagerRegistry $registry) { - parent::__construct($registry, ProjectReward::class); + parent::__construct($registry, Reward::class); } // /** - // * @return ProjectReward[] Returns an array of ProjectReward objects + // * @return Reward[] Returns an array of Reward objects // */ // public function findByExampleField($value): array // { @@ -31,7 +31,7 @@ public function __construct(ManagerRegistry $registry) // ; // } - // public function findOneBySomeField($value): ?ProjectReward + // public function findOneBySomeField($value): ?Reward // { // return $this->createQueryBuilder('p') // ->andWhere('p.exampleField = :val') From c285adedcae95d4f5a80bd57be283ec05b3ad371 Mon Sep 17 00:00:00 2001 From: Daniel Subiabre Date: Thu, 28 Nov 2024 12:28:53 +0100 Subject: [PATCH 04/36] Fix bugs from project namespace move --- src/Entity/Accounting/Accounting.php | 2 +- src/Entity/User.php | 1 + src/Library/Benzina/Pump/CheckoutsPump.php | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Entity/Accounting/Accounting.php b/src/Entity/Accounting/Accounting.php index 1695bf7..f19e377 100644 --- a/src/Entity/Accounting/Accounting.php +++ b/src/Entity/Accounting/Accounting.php @@ -3,7 +3,7 @@ namespace App\Entity\Accounting; use App\Entity\Interface\AccountingOwnerInterface; -use App\Entity\Project; +use App\Entity\Project\Project; use App\Entity\Tipjar; use App\Entity\User; use App\Repository\Accounting\AccountingRepository; diff --git a/src/Entity/User.php b/src/Entity/User.php index 35d74b8..afa5a83 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -6,6 +6,7 @@ use App\Entity\Accounting\Accounting; use App\Entity\Interface\AccountingOwnerInterface; use App\Entity\Interface\UserOwnedInterface; +use App\Entity\Project\Project; use App\Entity\Trait\MigratedEntity; use App\Entity\Trait\TimestampedCreationEntity; use App\Entity\Trait\TimestampedUpdationEntity; diff --git a/src/Library/Benzina/Pump/CheckoutsPump.php b/src/Library/Benzina/Pump/CheckoutsPump.php index cf54a5b..9ce1a3d 100644 --- a/src/Library/Benzina/Pump/CheckoutsPump.php +++ b/src/Library/Benzina/Pump/CheckoutsPump.php @@ -17,7 +17,7 @@ use App\Gateway\Wallet\WalletGateway; use App\Library\Benzina\Pump\Trait\ArrayPumpTrait; use App\Library\Benzina\Pump\Trait\DoctrinePumpTrait; -use App\Repository\ProjectRepository; +use App\Repository\Project\ProjectRepository; use App\Repository\TipjarRepository; use App\Repository\UserRepository; use App\Service\Gateway\CheckoutService; From 8440f16cc83f0f481a399390a0123bf02f392ce5 Mon Sep 17 00:00:00 2001 From: Daniel Subiabre Date: Thu, 28 Nov 2024 12:32:58 +0100 Subject: [PATCH 05/36] Simplify accounting creation with specified ownership --- src/Entity/Accounting/Accounting.php | 11 +++++++++++ src/Entity/Project/Project.php | 5 +---- src/Entity/Tipjar.php | 5 +---- src/Entity/User.php | 5 +---- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/Entity/Accounting/Accounting.php b/src/Entity/Accounting/Accounting.php index f19e377..8c8700a 100644 --- a/src/Entity/Accounting/Accounting.php +++ b/src/Entity/Accounting/Accounting.php @@ -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() { /* diff --git a/src/Entity/Project/Project.php b/src/Entity/Project/Project.php index cb42bbc..7e619b3 100644 --- a/src/Entity/Project/Project.php +++ b/src/Entity/Project/Project.php @@ -60,10 +60,7 @@ class Project implements AccountingOwnerInterface public function __construct() { - $accounting = new Accounting(); - $accounting->setOwner($this); - - $this->accounting = $accounting; + $this->accounting = Accounting::of($this); $this->rewards = new ArrayCollection(); } diff --git a/src/Entity/Tipjar.php b/src/Entity/Tipjar.php index 621bd84..8a177e7 100644 --- a/src/Entity/Tipjar.php +++ b/src/Entity/Tipjar.php @@ -40,10 +40,7 @@ class Tipjar implements AccountingOwnerInterface public function __construct() { - $accounting = new Accounting(); - $accounting->setOwner($this); - - $this->accounting = $accounting; + $this->accounting = Accounting::of($this); } public function getId(): ?int diff --git a/src/Entity/User.php b/src/Entity/User.php index afa5a83..e8f01ac 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -141,10 +141,7 @@ class User implements UserInterface, UserOwnedInterface, PasswordAuthenticatedUs public function __construct() { - $accounting = new Accounting(); - $accounting->setOwner($this); - - $this->accounting = $accounting; + $this->accounting = Accounting::of($this); $this->emailConfirmed = false; $this->active = false; From d010ebb202150a2dc266b4b93dbf06d8376cd7cf Mon Sep 17 00:00:00 2001 From: Daniel Subiabre Date: Thu, 28 Nov 2024 13:11:59 +0100 Subject: [PATCH 06/36] Add dependency on jolicode/automapper --- composer.json | 1 + composer.lock | 289 +++++++++++++++++++++++++++++++++++---------- config/bundles.php | 1 + symfony.lock | 12 ++ 4 files changed, 241 insertions(+), 62 deletions(-) diff --git a/composer.json b/composer.json index d0b66f6..393466d 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 7d3f381..5075c35 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": "0d0e2940f9aaecab4ba3c50bc5822de6", + "content-hash": "606fea65b8befe4e0c0a4698dd4b24d7", "packages": [ { "name": "api-platform/core", @@ -2119,6 +2119,92 @@ ], "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", @@ -2282,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", @@ -5174,6 +5318,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", @@ -8297,64 +8520,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", @@ -11152,7 +11317,7 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": {}, + "stability-flags": [], "prefer-stable": true, "prefer-lowest": false, "platform": { @@ -11160,6 +11325,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 19b2cda..31eef86 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/symfony.lock b/symfony.lock index d601f29..28dbadb 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": { From 731561587ac742e4e27d1538e9bafe55ad97b4cb Mon Sep 17 00:00:00 2001 From: Daniel Subiabre Date: Thu, 28 Nov 2024 13:12:18 +0100 Subject: [PATCH 07/36] Add symfony/lock config --- .env | 6 ++++++ config/packages/lock.yaml | 2 ++ 2 files changed, 8 insertions(+) create mode 100644 config/packages/lock.yaml diff --git a/.env b/.env index ff59b19..fbd996b 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/config/packages/lock.yaml b/config/packages/lock.yaml new file mode 100644 index 0000000..574879f --- /dev/null +++ b/config/packages/lock.yaml @@ -0,0 +1,2 @@ +framework: + lock: '%env(LOCK_DSN)%' From 420c5312e51d694b2e089078f73c9e8b144472a9 Mon Sep 17 00:00:00 2001 From: Daniel Subiabre Date: Thu, 28 Nov 2024 13:18:39 +0100 Subject: [PATCH 08/36] Fix users pump --- src/Library/Benzina/Pump/ProjectsPump.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Library/Benzina/Pump/ProjectsPump.php b/src/Library/Benzina/Pump/ProjectsPump.php index dd0edb9..ee30f48 100644 --- a/src/Library/Benzina/Pump/ProjectsPump.php +++ b/src/Library/Benzina/Pump/ProjectsPump.php @@ -3,8 +3,8 @@ namespace App\Library\Benzina\Pump; use App\Entity\Accounting\Accounting; -use App\Entity\Project; -use App\Entity\ProjectStatus; +use App\Entity\Project\Project; +use App\Entity\Project\ProjectStatus; use App\Entity\User; use App\Library\Benzina\Pump\Trait\ArrayPumpTrait; use App\Library\Benzina\Pump\Trait\DoctrinePumpTrait; @@ -159,20 +159,20 @@ private function getOwners(array $record): array private function getProjectStatus(int $status): ProjectStatus { switch ($status) { - case 0: - return ProjectStatus::Rejected; case 1: - return ProjectStatus::Editing; + return ProjectStatus::InEditing; case 2: - return ProjectStatus::Reviewing; + return ProjectStatus::InReview; + case 0: + return ProjectStatus::Rejected; case 3: return ProjectStatus::InCampaign; + case 6: + return ProjectStatus::Unfunded; case 4: - return ProjectStatus::Funded; + return ProjectStatus::InFunding; case 5: return ProjectStatus::Fulfilled; - case 6: - return ProjectStatus::Unfunded; } } From 469db67dffbea837514943b1b2bc67fc4d32c8a3 Mon Sep 17 00:00:00 2001 From: Daniel Subiabre Date: Thu, 28 Nov 2024 13:48:49 +0100 Subject: [PATCH 09/36] Add project custom api resource --- src/ApiResource/Project/Project.php | 50 ++++++++++++++++++++++ src/State/Project/ProjectStateProvider.php | 44 +++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 src/ApiResource/Project/Project.php create mode 100644 src/State/Project/ProjectStateProvider.php diff --git a/src/ApiResource/Project/Project.php b/src/ApiResource/Project/Project.php new file mode 100644 index 0000000..a52182f --- /dev/null +++ b/src/ApiResource/Project/Project.php @@ -0,0 +1,50 @@ +collectionProvider->provide($operation, $uriVariables, $context); + + $resources = []; + foreach ($collection as $entity) { + $project = $this->autoMapper->map($entity, Resource\Project::class); + + var_dump($project); + exit; + } + + return new TraversablePaginator( + new ArrayCollection($resources), + $collection->getCurrentPage(), + $collection->getItemsPerPage(), + $collection->getTotalItems() + ); + } + } +} From df19c7faa456c85ffb644a6b17fe77ccf2603d30 Mon Sep 17 00:00:00 2001 From: Daniel Subiabre Date: Thu, 28 Nov 2024 16:41:29 +0100 Subject: [PATCH 10/36] Temporary mapping fixes --- src/ApiResource/Project/Project.php | 2 +- src/Entity/User.php | 12 +++++++++++- src/State/Project/ProjectStateProvider.php | 5 +---- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/ApiResource/Project/Project.php b/src/ApiResource/Project/Project.php index a52182f..d4fa93d 100644 --- a/src/ApiResource/Project/Project.php +++ b/src/ApiResource/Project/Project.php @@ -45,6 +45,6 @@ class Project * The current status of this Project.\ * Admin-only property. */ - #[API\ApiProperty(security: 'is_granted("ROLE_ADMIN")')] + #[API\ApiProperty(securityPostDenormalize: 'is_granted("ROLE_ADMIN")')] public ProjectStatus $status = ProjectStatus::InEditing; } diff --git a/src/Entity/User.php b/src/Entity/User.php index e8f01ac..300b720 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -13,6 +13,7 @@ use App\Filter\OrderedLikeFilter; use App\Filter\UserQueryFilter; use App\Repository\UserRepository; +use AutoMapper\Attribute\MapTo; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; @@ -27,7 +28,7 @@ * Users represent people who interact with the platform.\ * \ * Users are the usual issuers of funding, however an User's Accounting can still be a Transaction recipient. - * This allows to keep an User's "wallet", witholding their non-raised fundings into their Accounting. + * This allows to keep an User's "wallet", withholding their non-raised fundings into their Accounting. */ #[Gedmo\Loggable()] #[API\GetCollection()] @@ -155,6 +156,13 @@ public function getId(): ?int return $this->id; } + public function setId(int $id): static + { + $this->id = $id; + + return $this; + } + public function getUsername(): ?string { return $this->username; @@ -232,6 +240,7 @@ public function setPassword(string $password): static return $this; } + #[MapTo(ignore: true)] public function getPlainPassword(): ?string { return $this->plainPassword; @@ -385,6 +394,7 @@ public function removeProject(Project $project): static return $this; } + #[MapTo(if: 'getPersonalData')] public function getPersonalData(): ?UserPersonal { return $this->personalData; diff --git a/src/State/Project/ProjectStateProvider.php b/src/State/Project/ProjectStateProvider.php index d638fc7..7ed56cc 100644 --- a/src/State/Project/ProjectStateProvider.php +++ b/src/State/Project/ProjectStateProvider.php @@ -27,10 +27,7 @@ public function provide(Operation $operation, array $uriVariables = [], array $c $resources = []; foreach ($collection as $entity) { - $project = $this->autoMapper->map($entity, Resource\Project::class); - - var_dump($project); - exit; + $resources[] = $this->autoMapper->map($entity, Resource\Project::class); } return new TraversablePaginator( From 677be48e4fc618f368e654ba245802c30e707a12 Mon Sep 17 00:00:00 2001 From: Daniel Subiabre Date: Mon, 2 Dec 2024 14:38:33 +0100 Subject: [PATCH 11/36] Update user owned interface to include owner setter --- src/Entity/Interface/UserOwnedInterface.php | 2 ++ src/Entity/Project/Project.php | 15 ++++++++++++++- src/Entity/User.php | 5 +++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/Entity/Interface/UserOwnedInterface.php b/src/Entity/Interface/UserOwnedInterface.php index 076b4b3..cabf07c 100644 --- a/src/Entity/Interface/UserOwnedInterface.php +++ b/src/Entity/Interface/UserOwnedInterface.php @@ -9,4 +9,6 @@ interface UserOwnedInterface public function getOwner(): ?User; public function isOwnedBy(User $user): bool; + + public function setOwner(User $user): static; } diff --git a/src/Entity/Project/Project.php b/src/Entity/Project/Project.php index 7e619b3..31b4da7 100644 --- a/src/Entity/Project/Project.php +++ b/src/Entity/Project/Project.php @@ -4,6 +4,7 @@ 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; @@ -14,7 +15,7 @@ use Doctrine\ORM\Mapping as ORM; #[ORM\Entity(repositoryClass: ProjectRepository::class)] -class Project implements AccountingOwnerInterface +class Project implements UserOwnedInterface, AccountingOwnerInterface { use MigratedEntity; use TimestampedCreationEntity; @@ -69,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; @@ -98,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; diff --git a/src/Entity/User.php b/src/Entity/User.php index 300b720..a6ef3d9 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -196,6 +196,11 @@ public function isOwnedBy(User $user): bool return $this->getUserIdentifier() === $user->getUserIdentifier(); } + public function setOwner(User $user): static + { + return $this; + } + /** * @see UserInterface * From 7b26498bb3e77107887315117356c4644ee3e766 Mon Sep 17 00:00:00 2001 From: Daniel Subiabre Date: Mon, 2 Dec 2024 14:38:55 +0100 Subject: [PATCH 12/36] Only map migratedId when migrated --- src/Entity/Trait/MigratedEntity.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Entity/Trait/MigratedEntity.php b/src/Entity/Trait/MigratedEntity.php index 07a3135..e8bd672 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; From eddbb9d7823347fcd0a5789c960315c4587c1b39 Mon Sep 17 00:00:00 2001 From: Daniel Subiabre Date: Mon, 2 Dec 2024 14:39:10 +0100 Subject: [PATCH 13/36] Fix project api tests --- tests/Entity/ProjectApiTest.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/Entity/ProjectApiTest.php b/tests/Entity/ProjectApiTest.php index ab1978f..263657c 100644 --- a/tests/Entity/ProjectApiTest.php +++ b/tests/Entity/ProjectApiTest.php @@ -3,8 +3,8 @@ namespace App\Tests\Entity; use ApiPlatform\Symfony\Bundle\Test\ApiTestCase; -use App\Entity\Project; -use App\Entity\ProjectStatus; +use App\Entity\Project\Project; +use App\Entity\Project\ProjectStatus; use App\Entity\User; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\HttpFoundation\Response; @@ -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' => [], ], ]]); } From 9f5b8f9fed26a355fcad05d11379a2d0ec24d9bb Mon Sep 17 00:00:00 2001 From: Daniel Subiabre Date: Mon, 2 Dec 2024 14:39:39 +0100 Subject: [PATCH 14/36] Add reward api resource --- src/ApiResource/Project/Reward.php | 67 ++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 src/ApiResource/Project/Reward.php diff --git a/src/ApiResource/Project/Reward.php b/src/ApiResource/Project/Reward.php new file mode 100644 index 0000000..5e1c7bc --- /dev/null +++ b/src/ApiResource/Project/Reward.php @@ -0,0 +1,67 @@ + Date: Mon, 2 Dec 2024 14:39:48 +0100 Subject: [PATCH 15/36] Add generic api resource provider --- src/State/ApiResourceStateProvider.php | 49 ++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 src/State/ApiResourceStateProvider.php diff --git a/src/State/ApiResourceStateProvider.php b/src/State/ApiResourceStateProvider.php new file mode 100644 index 0000000..8fe0a35 --- /dev/null +++ b/src/State/ApiResourceStateProvider.php @@ -0,0 +1,49 @@ +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 ArrayCollection($resources), + $collection->getCurrentPage(), + $collection->getItemsPerPage(), + $collection->getTotalItems() + ); + } + + $item = $this->itemProvider->provide($operation, $uriVariables, $context); + + return $this->autoMapper->map($item, $resourceClass); + } +} From 71f8e9811778a5937e4c27869c6a9e3d676fc182 Mon Sep 17 00:00:00 2001 From: Daniel Subiabre Date: Mon, 2 Dec 2024 14:40:01 +0100 Subject: [PATCH 16/36] Update project api resource --- src/ApiResource/Project/Project.php | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/ApiResource/Project/Project.php b/src/ApiResource/Project/Project.php index d4fa93d..cdc056c 100644 --- a/src/ApiResource/Project/Project.php +++ b/src/ApiResource/Project/Project.php @@ -2,12 +2,14 @@ namespace App\ApiResource\Project; +use ApiPlatform\Doctrine\Orm\Filter\SearchFilter; use ApiPlatform\Doctrine\Orm\State\Options; use ApiPlatform\Metadata as API; use App\ApiResource\Accounting\Accounting; use App\Entity\Project as Entity; use App\Entity\Project\ProjectStatus; use App\Entity\User; +use App\State\ApiResourceStateProcessor; use App\State\Project\ProjectStateProvider; use Symfony\Component\Validator\Constraints as Assert; @@ -16,8 +18,14 @@ */ #[API\ApiResource( stateOptions: new Options(entityClass: Entity\Project::class), - provider: ProjectStateProvider::class + provider: ProjectStateProvider::class, + processor: ApiResourceStateProcessor::class )] +#[API\GetCollection()] +#[API\Post(security: 'is_granted("ROLE_USER")')] +#[API\Get()] +#[API\Patch(security: 'is_granted("PROJECT_EDIT")')] +#[API\Delete(security: 'is_granted("PROJECT_EDIT")')] class Project { #[API\ApiProperty(identifier: true, writable: false)] @@ -26,6 +34,7 @@ class Project /** * The User who launched this Project. */ + #[API\ApiFilter(filterClass: SearchFilter::class, strategy: 'exact')] #[API\ApiProperty(writable: false)] public User $owner; @@ -38,6 +47,7 @@ class Project /** * Main title for this Project. */ + #[API\ApiFilter(filterClass: SearchFilter::class, strategy: 'partial')] #[Assert\NotBlank()] public string $title; @@ -45,6 +55,10 @@ class Project * The current status of this Project.\ * Admin-only property. */ + #[API\ApiFilter(filterClass: SearchFilter::class, strategy: 'exact')] #[API\ApiProperty(securityPostDenormalize: 'is_granted("ROLE_ADMIN")')] public ProjectStatus $status = ProjectStatus::InEditing; + + /** @var array */ + public array $rewards = []; } From 0950604465d58918d2efeeb9807f9508edeb9bab Mon Sep 17 00:00:00 2001 From: Daniel Subiabre Date: Mon, 2 Dec 2024 14:40:15 +0100 Subject: [PATCH 17/36] Add generic api resource state processor --- src/State/ApiResourceStateProcessor.php | 75 +++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 src/State/ApiResourceStateProcessor.php diff --git a/src/State/ApiResourceStateProcessor.php b/src/State/ApiResourceStateProcessor.php new file mode 100644 index 0000000..d6cbf80 --- /dev/null +++ b/src/State/ApiResourceStateProcessor.php @@ -0,0 +1,75 @@ +getStateOptions(); + $entityClass = $stateOptions->getEntityClass(); + + $target = new $entityClass(); + + /** @var object */ + $entity = $this->autoMapper->map($data, $target, $this->buildMappingContext($data, $target)); + + if ($operation instanceof DeleteOperationInterface) { + $this->removeProcessor->process($entity, $operation, $uriVariables, $context); + + return null; + } + + if ($entity->getId() === null && $entity instanceof UserOwnedInterface) { + $entity->setOwner($this->security->getUser()); + } + + $this->persistProcessor->process($entity, $operation, $uriVariables, $context); + $data->id = $entity->getId(); + + return $data; + } + + private function buildMappingContext(object $source, object $target): array + { + $ignoredAttributes = []; + + if (!isset($source->id)) { + \array_push($ignoredAttributes, 'id'); + + if ($target instanceof UserOwnedInterface && !isset($source->owner)) { + \array_push($ignoredAttributes, 'owner'); + } + + if ($target instanceof AccountingOwnerInterface && !isset($source->accounting)) { + \array_push($ignoredAttributes, 'accounting'); + } + } + + return [ + 'ignored_attributes' => $ignoredAttributes + ]; + } +} From c9f9138d074f9c61b9c513596a3e786179ad68d1 Mon Sep 17 00:00:00 2001 From: Daniel Subiabre Date: Mon, 2 Dec 2024 14:41:18 +0100 Subject: [PATCH 18/36] CS Fixer fixes --- config/bundles.php | 2 +- src/State/ApiResourceStateProcessor.php | 8 ++++---- src/State/Project/ProjectStateProvider.php | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/config/bundles.php b/config/bundles.php index 31eef86..9dbe7c2 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -13,5 +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] + AutoMapper\Symfony\Bundle\AutoMapperBundle::class => ['all' => true], ]; diff --git a/src/State/ApiResourceStateProcessor.php b/src/State/ApiResourceStateProcessor.php index d6cbf80..daf749f 100644 --- a/src/State/ApiResourceStateProcessor.php +++ b/src/State/ApiResourceStateProcessor.php @@ -4,13 +4,13 @@ use ApiPlatform\Doctrine\Common\State\PersistProcessor; use ApiPlatform\Doctrine\Common\State\RemoveProcessor; -use ApiPlatform\Metadata\Operation; -use ApiPlatform\State\ProcessorInterface; -use AutoMapper\AutoMapperInterface; use ApiPlatform\Doctrine\Orm\State\Options; use ApiPlatform\Metadata\DeleteOperationInterface; +use ApiPlatform\Metadata\Operation; +use ApiPlatform\State\ProcessorInterface; use App\Entity\Interface\AccountingOwnerInterface; use App\Entity\Interface\UserOwnedInterface; +use AutoMapper\AutoMapperInterface; use Symfony\Bundle\SecurityBundle\Security; use Symfony\Component\DependencyInjection\Attribute\Autowire; @@ -69,7 +69,7 @@ private function buildMappingContext(object $source, object $target): array } return [ - 'ignored_attributes' => $ignoredAttributes + 'ignored_attributes' => $ignoredAttributes, ]; } } diff --git a/src/State/Project/ProjectStateProvider.php b/src/State/Project/ProjectStateProvider.php index 7ed56cc..85c274d 100644 --- a/src/State/Project/ProjectStateProvider.php +++ b/src/State/Project/ProjectStateProvider.php @@ -17,7 +17,7 @@ class ProjectStateProvider implements ProviderInterface public function __construct( private AutoMapperInterface $autoMapper, #[Autowire(service: CollectionProvider::class)] - private ProviderInterface $collectionProvider + private ProviderInterface $collectionProvider, ) {} public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null From a8edbf80a8d23e046f62dcd63ca4542da9c5f587 Mon Sep 17 00:00:00 2001 From: Daniel Subiabre Date: Mon, 2 Dec 2024 14:49:06 +0100 Subject: [PATCH 19/36] Update PHP versions to 8.3 --- .github/workflows/php-cs-fixer.yaml | 4 ++-- .github/workflows/phpunit.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/php-cs-fixer.yaml b/.github/workflows/php-cs-fixer.yaml index 1c3dea8..c0f0dd7 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 2d80aba..55883b7 100644 --- a/.github/workflows/phpunit.yaml +++ b/.github/workflows/phpunit.yaml @@ -26,6 +26,6 @@ jobs: 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 From 387c17a6624f7a5c366f3149b1746a1f3b66c753 Mon Sep 17 00:00:00 2001 From: Daniel Subiabre Date: Mon, 2 Dec 2024 14:52:22 +0100 Subject: [PATCH 20/36] Pass php version as string --- .github/workflows/phpunit.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/phpunit.yaml b/.github/workflows/phpunit.yaml index 55883b7..285b0bf 100644 --- a/.github/workflows/phpunit.yaml +++ b/.github/workflows/phpunit.yaml @@ -26,6 +26,6 @@ jobs: env: DATABASE_URL: mysql://root:goteo@127.0.0.1:${{ job.services.mariadb.ports['3306'] }}/goteo with: - php_version: 8.3 + php_version: '8.3' php_extensions: pdo_mysql configuration: phpunit.xml.dist From fa435ba14aa23f178fc825f9859014f66778a215 Mon Sep 17 00:00:00 2001 From: Daniel Subiabre Date: Mon, 2 Dec 2024 15:03:05 +0100 Subject: [PATCH 21/36] Update phpunit gh action config for php8.3 --- .github/workflows/phpunit.yaml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/phpunit.yaml b/.github/workflows/phpunit.yaml index 285b0bf..8e1b527 100644 --- a/.github/workflows/phpunit.yaml +++ b/.github/workflows/phpunit.yaml @@ -17,15 +17,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.3' + php_version: 8.3 php_extensions: pdo_mysql configuration: phpunit.xml.dist From 790e955e3a867d07c1975919cf3cf3887d9902e2 Mon Sep 17 00:00:00 2001 From: Daniel Subiabre Date: Mon, 2 Dec 2024 15:05:42 +0100 Subject: [PATCH 22/36] Separate composer step --- .github/workflows/phpunit.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/phpunit.yaml b/.github/workflows/phpunit.yaml index 8e1b527..0122ac8 100644 --- a/.github/workflows/phpunit.yaml +++ b/.github/workflows/phpunit.yaml @@ -17,6 +17,7 @@ jobs: steps: - uses: actions/checkout@v4 + - uses: php-actions/composer@v6 - name: PHPUnit From 91459afe148a086e72fd9b72a991556fce1988f6 Mon Sep 17 00:00:00 2001 From: Daniel Subiabre Date: Tue, 3 Dec 2024 14:14:17 +0100 Subject: [PATCH 23/36] Add @return type annotation --- src/State/ApiResourceStateProcessor.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/State/ApiResourceStateProcessor.php b/src/State/ApiResourceStateProcessor.php index daf749f..92be81c 100644 --- a/src/State/ApiResourceStateProcessor.php +++ b/src/State/ApiResourceStateProcessor.php @@ -25,6 +25,9 @@ public function __construct( private Security $security, ) {} + /** + * @return mixed + */ public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []) { /** @var Options */ From 7b4c91471b833b053f87ce4a7cfef64a222e7dcc Mon Sep 17 00:00:00 2001 From: Daniel Subiabre Date: Wed, 4 Dec 2024 10:51:54 +0100 Subject: [PATCH 24/36] Add reward claim entity --- src/Entity/Project/Reward.php | 43 ++++++++++++++ src/Entity/Project/RewardClaim.php | 59 +++++++++++++++++++ .../Project/RewardClaimRepository.php | 43 ++++++++++++++ 3 files changed, 145 insertions(+) create mode 100644 src/Entity/Project/RewardClaim.php create mode 100644 src/Repository/Project/RewardClaimRepository.php diff --git a/src/Entity/Project/Reward.php b/src/Entity/Project/Reward.php index e690443..416af34 100644 --- a/src/Entity/Project/Reward.php +++ b/src/Entity/Project/Reward.php @@ -4,6 +4,8 @@ use App\Entity\Money; use App\Repository\Project\RewardRepository; +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; @@ -52,6 +54,17 @@ class Reward #[ORM\Column] private ?int $unitsAvailable = null; + /** + * @var Collection + */ + #[ORM\OneToMany(mappedBy: 'reward', targetEntity: RewardClaim::class)] + private Collection $claims; + + public function __construct() + { + $this->claims = new ArrayCollection(); + } + public function getId(): ?int { return $this->id; @@ -140,4 +153,34 @@ public function setUnitsAvailable(int $unitsAvailable): static 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 0000000..755b1ee --- /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/Repository/Project/RewardClaimRepository.php b/src/Repository/Project/RewardClaimRepository.php new file mode 100644 index 0000000..a4d9222 --- /dev/null +++ b/src/Repository/Project/RewardClaimRepository.php @@ -0,0 +1,43 @@ + + */ +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() + // ; + // } +} From 40b5e5aff2a00d7d248f441e34a26e673ba845d8 Mon Sep 17 00:00:00 2001 From: Daniel Subiabre Date: Wed, 4 Dec 2024 11:09:13 +0100 Subject: [PATCH 25/36] Sub project state provider by generic resource provider --- src/ApiResource/Project/Project.php | 4 +-- src/State/Project/ProjectStateProvider.php | 41 ---------------------- 2 files changed, 2 insertions(+), 43 deletions(-) delete mode 100644 src/State/Project/ProjectStateProvider.php diff --git a/src/ApiResource/Project/Project.php b/src/ApiResource/Project/Project.php index cdc056c..12882e0 100644 --- a/src/ApiResource/Project/Project.php +++ b/src/ApiResource/Project/Project.php @@ -10,7 +10,7 @@ use App\Entity\Project\ProjectStatus; use App\Entity\User; use App\State\ApiResourceStateProcessor; -use App\State\Project\ProjectStateProvider; +use App\State\ApiResourceStateProvider; use Symfony\Component\Validator\Constraints as Assert; /** @@ -18,7 +18,7 @@ */ #[API\ApiResource( stateOptions: new Options(entityClass: Entity\Project::class), - provider: ProjectStateProvider::class, + provider: ApiResourceStateProvider::class, processor: ApiResourceStateProcessor::class )] #[API\GetCollection()] diff --git a/src/State/Project/ProjectStateProvider.php b/src/State/Project/ProjectStateProvider.php deleted file mode 100644 index 85c274d..0000000 --- a/src/State/Project/ProjectStateProvider.php +++ /dev/null @@ -1,41 +0,0 @@ -collectionProvider->provide($operation, $uriVariables, $context); - - $resources = []; - foreach ($collection as $entity) { - $resources[] = $this->autoMapper->map($entity, Resource\Project::class); - } - - return new TraversablePaginator( - new ArrayCollection($resources), - $collection->getCurrentPage(), - $collection->getItemsPerPage(), - $collection->getTotalItems() - ); - } - } -} From a854cbc382c39fd988d89a2b761c624a83d0f46f Mon Sep 17 00:00:00 2001 From: Daniel Subiabre Date: Wed, 4 Dec 2024 11:09:28 +0100 Subject: [PATCH 26/36] Return null on no item for generic resource provider --- src/State/ApiResourceStateProvider.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/State/ApiResourceStateProvider.php b/src/State/ApiResourceStateProvider.php index 8fe0a35..354cbf2 100644 --- a/src/State/ApiResourceStateProvider.php +++ b/src/State/ApiResourceStateProvider.php @@ -44,6 +44,10 @@ public function provide(Operation $operation, array $uriVariables = [], array $c $item = $this->itemProvider->provide($operation, $uriVariables, $context); + if (!$item) { + return null; + } + return $this->autoMapper->map($item, $resourceClass); } } From d4cc1a91a26c644077c42342e203bee69d9d3769 Mon Sep 17 00:00:00 2001 From: Daniel Subiabre Date: Wed, 4 Dec 2024 11:21:52 +0100 Subject: [PATCH 27/36] Fix reward resource attributes --- src/ApiResource/Project/Reward.php | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/ApiResource/Project/Reward.php b/src/ApiResource/Project/Reward.php index 5e1c7bc..7696f00 100644 --- a/src/ApiResource/Project/Reward.php +++ b/src/ApiResource/Project/Reward.php @@ -8,6 +8,7 @@ use App\Entity\Project as Entity; use App\State\ApiResourceStateProcessor; use App\State\ApiResourceStateProvider; +use AutoMapper\Attribute\MapTo; use Symfony\Component\Validator\Constraints as Assert; /** @@ -39,7 +40,8 @@ class Reward /** * Detailed information about this reward. */ - public string $description; + #[MapTo(if: 'source.description != null')] + public ?string $description = null; /** * The minimal monetary sum to be able to claim this reward. @@ -50,18 +52,22 @@ class Reward /** * Rewards might be finite, i.e: has a limited amount of existing unitsTotal. */ - #[Assert\NotBlank()] + #[Assert\NotNull()] + #[Assert\Type('bool')] public bool $hasUnits; /** * For finite rewards, the total amount of existing unitsTotal. */ - #[Assert\NotBlank()] - public int $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; + public int $unitsAvailable = 0; } From a95d75d8a5ee93cdcbca0d2b56c8d5b1aa96714e Mon Sep 17 00:00:00 2001 From: Daniel Subiabre Date: Wed, 4 Dec 2024 11:27:06 +0100 Subject: [PATCH 28/36] Update cascade persistence --- src/Entity/Project/Project.php | 2 +- src/Entity/Project/Reward.php | 2 +- src/Entity/User.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Entity/Project/Project.php b/src/Entity/Project/Project.php index 31b4da7..4bbf3d6 100644 --- a/src/Entity/Project/Project.php +++ b/src/Entity/Project/Project.php @@ -42,7 +42,7 @@ class Project implements UserOwnedInterface, AccountingOwnerInterface /** * The User who created this Project. */ - #[ORM\ManyToOne(inversedBy: 'projects')] + #[ORM\ManyToOne(inversedBy: 'projects', cascade: ['persist'])] #[ORM\JoinColumn(nullable: false)] private ?User $owner = null; diff --git a/src/Entity/Project/Reward.php b/src/Entity/Project/Reward.php index 416af34..7d9e4e1 100644 --- a/src/Entity/Project/Reward.php +++ b/src/Entity/Project/Reward.php @@ -20,7 +20,7 @@ class Reward #[ORM\Column] private ?int $id = null; - #[ORM\ManyToOne(inversedBy: 'rewards')] + #[ORM\ManyToOne(inversedBy: 'rewards', cascade: ['persist'])] #[ORM\JoinColumn(nullable: false)] private ?Project $project = null; diff --git a/src/Entity/User.php b/src/Entity/User.php index a6ef3d9..d02e135 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -134,7 +134,7 @@ class User implements UserInterface, UserOwnedInterface, PasswordAuthenticatedUs * The projects owned by this User. */ #[API\ApiProperty(writable: false)] - #[ORM\OneToMany(mappedBy: 'owner', targetEntity: Project::class)] + #[ORM\OneToMany(mappedBy: 'owner', targetEntity: Project::class, cascade: ['persist'])] private Collection $projects; #[ORM\OneToOne(mappedBy: 'user', cascade: ['persist', 'remove'])] From 03dbcbc9fc1b8cae2a2b19f6072f743808b5ecba Mon Sep 17 00:00:00 2001 From: Daniel Subiabre Date: Wed, 4 Dec 2024 11:29:42 +0100 Subject: [PATCH 29/36] Sub user state processor decoration for explicit usage via attribute --- src/Entity/User.php | 2 ++ src/State/UserStateProcessor.php | 9 +++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Entity/User.php b/src/Entity/User.php index d02e135..b0b55e5 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -13,6 +13,7 @@ use App\Filter\OrderedLikeFilter; use App\Filter\UserQueryFilter; use App\Repository\UserRepository; +use App\State\UserStateProcessor; use AutoMapper\Attribute\MapTo; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; @@ -31,6 +32,7 @@ * This allows to keep an User's "wallet", withholding their non-raised fundings into their Accounting. */ #[Gedmo\Loggable()] +#[API\ApiResource(processor: UserStateProcessor::class)] #[API\GetCollection()] #[API\Post(validationContext: ['groups' => ['default', 'postValidation']])] #[API\Get()] diff --git a/src/State/UserStateProcessor.php b/src/State/UserStateProcessor.php index f9d1452..9a20cc3 100644 --- a/src/State/UserStateProcessor.php +++ b/src/State/UserStateProcessor.php @@ -2,17 +2,18 @@ namespace App\State; +use ApiPlatform\Doctrine\Common\State\PersistProcessor; use ApiPlatform\Metadata as API; use ApiPlatform\State\ProcessorInterface; use App\Entity\User; -use Symfony\Component\DependencyInjection\Attribute\AsDecorator; +use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; -#[AsDecorator('api_platform.doctrine.orm.state.persist_processor')] class UserStateProcessor implements ProcessorInterface { public function __construct( - private ProcessorInterface $innerProcessor, + #[Autowire(service: PersistProcessor::class)] + private ProcessorInterface $persistProcessor, private UserPasswordHasherInterface $userPasswordHasher, ) {} @@ -27,6 +28,6 @@ public function process(mixed $data, API\Operation $operation, array $uriVariabl ); } - return $this->innerProcessor->process($data, $operation, $uriVariables, $context); + return $this->persistProcessor->process($data, $operation, $uriVariables, $context); } } From 038e444525d33d02dc84dec2c0adcac5f2a7c93a Mon Sep 17 00:00:00 2001 From: Daniel Subiabre Date: Wed, 4 Dec 2024 13:06:28 +0100 Subject: [PATCH 30/36] Avoid mapping items already of resource class --- src/State/ApiResourceStateProvider.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/State/ApiResourceStateProvider.php b/src/State/ApiResourceStateProvider.php index 354cbf2..2ffbc9a 100644 --- a/src/State/ApiResourceStateProvider.php +++ b/src/State/ApiResourceStateProvider.php @@ -48,6 +48,10 @@ public function provide(Operation $operation, array $uriVariables = [], array $c return null; } + if ($item instanceof $resourceClass) { + return $item; + } + return $this->autoMapper->map($item, $resourceClass); } } From e67966ad3d0474342fa385aa289908d0402cbd3a Mon Sep 17 00:00:00 2001 From: Daniel Subiabre Date: Wed, 4 Dec 2024 14:45:11 +0100 Subject: [PATCH 31/36] Add AccountingService --- src/Mapping/Accounting/AccountingMapper.php | 35 +++------------- src/Service/AccountingService.php | 45 +++++++++++++++++++++ 2 files changed, 50 insertions(+), 30 deletions(-) create mode 100644 src/Service/AccountingService.php diff --git a/src/Mapping/Accounting/AccountingMapper.php b/src/Mapping/Accounting/AccountingMapper.php index ac8ef8d..174b892 100644 --- a/src/Mapping/Accounting/AccountingMapper.php +++ b/src/Mapping/Accounting/AccountingMapper.php @@ -4,13 +4,9 @@ use App\ApiResource\Accounting as Resource; use App\Entity\Accounting as Entity; -use App\Entity\Interface\AccountingOwnerInterface; use App\Entity\Money; -use App\Entity\User; -use App\Gateway\Wallet\WalletService; -use App\Library\Economy\MoneyService; use App\Repository\Accounting\AccountingRepository; -use App\Repository\Accounting\TransactionRepository; +use App\Service\AccountingService; use Doctrine\ORM\EntityManagerInterface; class AccountingMapper @@ -18,9 +14,7 @@ class AccountingMapper public function __construct( private EntityManagerInterface $entityManager, private AccountingRepository $accountingRepository, - private TransactionRepository $transactionRepository, - private WalletService $wallet, - private MoneyService $money, + private AccountingService $accountingService, ) {} public function toResource(Entity\Accounting $entity): Resource\Accounting @@ -31,7 +25,7 @@ public function toResource(Entity\Accounting $entity): Resource\Accounting $resource->id = $entity->getId(); $resource->currency = $entity->getCurrency(); $resource->owner = $owner; - $resource->balance = $this->getBalance($owner); + $resource->balance = $this->getBalance($entity); return $resource; } @@ -50,27 +44,8 @@ public function toEntity(Resource\Accounting $resource): Entity\Accounting return $entity; } - private function getBalance(AccountingOwnerInterface $owner): Money + private function getBalance(Entity\Accounting $accounting): 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; + return $this->accountingService->calcBalance($accounting); } } diff --git a/src/Service/AccountingService.php b/src/Service/AccountingService.php new file mode 100644 index 0000000..9a58d9c --- /dev/null +++ b/src/Service/AccountingService.php @@ -0,0 +1,45 @@ +getOwner(); + + 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; + } +} From 35704433c66775c72738366309d2d60d3d54ca6a Mon Sep 17 00:00:00 2001 From: Daniel Subiabre Date: Wed, 4 Dec 2024 15:23:21 +0100 Subject: [PATCH 32/36] Remove user owned interface from user --- src/Entity/User.php | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/src/Entity/User.php b/src/Entity/User.php index b0b55e5..6af9fb7 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -45,7 +45,7 @@ #[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; @@ -188,21 +188,6 @@ public function getUserIdentifier(): string return (string) $this->username; } - public function getOwner(): ?User - { - return $this; - } - - public function isOwnedBy(User $user): bool - { - return $this->getUserIdentifier() === $user->getUserIdentifier(); - } - - public function setOwner(User $user): static - { - return $this; - } - /** * @see UserInterface * From c34e41f10d6668d8cb9190579419fd526cfdb76c Mon Sep 17 00:00:00 2001 From: Daniel Subiabre Date: Thu, 5 Dec 2024 12:01:16 +0100 Subject: [PATCH 33/36] Add reward claim api resource --- src/ApiResource/Project/RewardClaim.php | 38 +++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 src/ApiResource/Project/RewardClaim.php diff --git a/src/ApiResource/Project/RewardClaim.php b/src/ApiResource/Project/RewardClaim.php new file mode 100644 index 0000000..c8b1d3d --- /dev/null +++ b/src/ApiResource/Project/RewardClaim.php @@ -0,0 +1,38 @@ + Date: Thu, 5 Dec 2024 12:03:18 +0100 Subject: [PATCH 34/36] PHP CS Fixer fixes --- src/Entity/User.php | 1 - src/State/ApiResourceStateProcessor.php | 5 +---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Entity/User.php b/src/Entity/User.php index 6af9fb7..e2324b3 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -5,7 +5,6 @@ use ApiPlatform\Metadata as API; use App\Entity\Accounting\Accounting; use App\Entity\Interface\AccountingOwnerInterface; -use App\Entity\Interface\UserOwnedInterface; use App\Entity\Project\Project; use App\Entity\Trait\MigratedEntity; use App\Entity\Trait\TimestampedCreationEntity; diff --git a/src/State/ApiResourceStateProcessor.php b/src/State/ApiResourceStateProcessor.php index 92be81c..9b94aab 100644 --- a/src/State/ApiResourceStateProcessor.php +++ b/src/State/ApiResourceStateProcessor.php @@ -25,10 +25,7 @@ public function __construct( private Security $security, ) {} - /** - * @return mixed - */ - public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []) + public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): object { /** @var Options */ $stateOptions = $operation->getStateOptions(); From 8e8bdf0a5928dfbf32f5c29b0bb92d6689c01b63 Mon Sep 17 00:00:00 2001 From: Daniel Subiabre Date: Wed, 11 Dec 2024 12:20:15 +0100 Subject: [PATCH 35/36] Comply with api resource class naming suffix --- src/ApiResource/Project/Project.php | 64 ------------------- .../{Reward.php => RewardApiResource.php} | 8 +-- ...rdClaim.php => RewardClaimApiResource.php} | 12 ++-- src/Entity/Project/RewardClaim.php | 2 +- 4 files changed, 11 insertions(+), 75 deletions(-) delete mode 100644 src/ApiResource/Project/Project.php rename src/ApiResource/Project/{Reward.php => RewardApiResource.php} (91%) rename src/ApiResource/Project/{RewardClaim.php => RewardClaimApiResource.php} (76%) diff --git a/src/ApiResource/Project/Project.php b/src/ApiResource/Project/Project.php deleted file mode 100644 index 12882e0..0000000 --- a/src/ApiResource/Project/Project.php +++ /dev/null @@ -1,64 +0,0 @@ - */ - public array $rewards = []; -} diff --git a/src/ApiResource/Project/Reward.php b/src/ApiResource/Project/RewardApiResource.php similarity index 91% rename from src/ApiResource/Project/Reward.php rename to src/ApiResource/Project/RewardApiResource.php index 7696f00..bf34027 100644 --- a/src/ApiResource/Project/Reward.php +++ b/src/ApiResource/Project/RewardApiResource.php @@ -5,7 +5,7 @@ use ApiPlatform\Doctrine\Orm\State\Options; use ApiPlatform\Metadata as API; use App\Entity\Money; -use App\Entity\Project as Entity; +use App\Entity\Project\Reward; use App\State\ApiResourceStateProcessor; use App\State\ApiResourceStateProvider; use AutoMapper\Attribute\MapTo; @@ -16,11 +16,11 @@ */ #[API\ApiResource( shortName: 'ProjectReward', - stateOptions: new Options(entityClass: Entity\Reward::class), + stateOptions: new Options(entityClass: Reward::class), provider: ApiResourceStateProvider::class, processor: ApiResourceStateProcessor::class )] -class Reward +class RewardApiResource { #[API\ApiProperty(identifier: true, writable: false)] public int $id; @@ -29,7 +29,7 @@ class Reward * The project which gives this reward. */ #[Assert\NotBlank()] - public Project $project; + public ProjectApiResource $project; /** * A short, descriptive title for this reward. diff --git a/src/ApiResource/Project/RewardClaim.php b/src/ApiResource/Project/RewardClaimApiResource.php similarity index 76% rename from src/ApiResource/Project/RewardClaim.php rename to src/ApiResource/Project/RewardClaimApiResource.php index c8b1d3d..153eabb 100644 --- a/src/ApiResource/Project/RewardClaim.php +++ b/src/ApiResource/Project/RewardClaimApiResource.php @@ -4,8 +4,8 @@ use ApiPlatform\Doctrine\Orm\State\Options; use ApiPlatform\Metadata as API; -use App\Entity\Project as Entity; -use App\Entity\User; +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; @@ -15,11 +15,11 @@ */ #[API\ApiResource( shortName: 'ProjectRewardClaim', - stateOptions: new Options(entityClass: Entity\Reward::class), + stateOptions: new Options(entityClass: RewardClaim::class), provider: ApiResourceStateProvider::class, processor: ApiResourceStateProcessor::class )] -class RewardClaim +class RewardClaimApiResource { #[API\ApiProperty(identifier: true, writable: false)] public int $id; @@ -28,11 +28,11 @@ class RewardClaim * The ProjectReward being claimed. */ #[Assert\NotBlank()] - public Reward $reward; + public RewardApiResource $reward; /** * The User claiming the ProjectReward. */ #[API\ApiProperty(writable: false)] - public User $owner; + public UserApiResource $owner; } diff --git a/src/Entity/Project/RewardClaim.php b/src/Entity/Project/RewardClaim.php index 755b1ee..4dc14fd 100644 --- a/src/Entity/Project/RewardClaim.php +++ b/src/Entity/Project/RewardClaim.php @@ -3,7 +3,7 @@ namespace App\Entity\Project; use App\Entity\Interface\UserOwnedInterface; -use App\Entity\User; +use App\Entity\User\User; use App\Repository\Project\RewardClaimRepository; use Doctrine\ORM\Mapping as ORM; From e1133b7dbc41253e96ed7a1add0a4832feaba372 Mon Sep 17 00:00:00 2001 From: Daniel Subiabre Date: Wed, 11 Dec 2024 12:22:53 +0100 Subject: [PATCH 36/36] Add rewards to project api resource --- src/ApiResource/Project/ProjectApiResource.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/ApiResource/Project/ProjectApiResource.php b/src/ApiResource/Project/ProjectApiResource.php index f8cf273..235bb21 100644 --- a/src/ApiResource/Project/ProjectApiResource.php +++ b/src/ApiResource/Project/ProjectApiResource.php @@ -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 + */ + public array $rewards; }