Merge pull request #71 from goteo/feat/project-updates

[feat] Project updates
This commit is contained in:
2025-02-26 13:07:32 +01:00
committed by GitHub
8 changed files with 335 additions and 20 deletions
@@ -0,0 +1,16 @@
<?php
namespace App\ApiResource;
use ApiPlatform\Metadata as API;
trait LocalizedApiResourceTrait
{
/**
* List of the available content locales.
*
* @var array<string>
*/
#[API\ApiProperty(writable: false)]
public array $locales;
}
@@ -5,6 +5,7 @@ namespace App\ApiResource\Project;
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
use ApiPlatform\Doctrine\Orm\State\Options;
use ApiPlatform\Metadata as API;
use App\ApiResource\LocalizedApiResourceTrait;
use App\ApiResource\Money;
use App\Entity\Project\BudgetItem;
use App\Entity\Project\BudgetItemType;
@@ -25,6 +26,8 @@ use Symfony\Component\Validator\Constraints as Assert;
)]
class BudgetItemApiResource
{
use LocalizedApiResourceTrait;
#[API\ApiProperty(identifier: true, writable: false)]
public int $id;
@@ -37,14 +40,6 @@ class BudgetItemApiResource
#[Assert\NotBlank()]
public BudgetItemType $type;
/**
* List of the available content locales.
*
* @var array<string>
*/
#[API\ApiProperty(writable: false)]
public array $locales;
/**
* A short, descriptive string for the item.
*/
+11 -8
View File
@@ -6,6 +6,7 @@ use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
use ApiPlatform\Doctrine\Orm\State\Options;
use ApiPlatform\Metadata as API;
use App\ApiResource\Accounting\AccountingApiResource;
use App\ApiResource\LocalizedApiResourceTrait;
use App\ApiResource\User\UserApiResource;
use App\Entity\Project\Project;
use App\Entity\Project\ProjectStatus;
@@ -34,6 +35,8 @@ use Symfony\Component\Validator\Constraints as Assert;
#[API\Delete(security: 'is_granted("PROJECT_EDIT", object)')]
class ProjectApiResource
{
use LocalizedApiResourceTrait;
#[API\ApiProperty(identifier: true, writable: false)]
public int $id;
@@ -49,14 +52,6 @@ class ProjectApiResource
#[API\ApiProperty(writable: false)]
public UserApiResource $owner;
/**
* List of the available content locales.
*
* @var array<string>
*/
#[API\ApiProperty(writable: false)]
public array $locales;
/**
* Main headline for the Project.
*/
@@ -129,4 +124,12 @@ class ProjectApiResource
*/
#[API\ApiProperty(writable: false)]
public array $budgetItems;
/**
* A list of the ProjectUpdates this Project has.
*
* @var array<int, UpdateApiResource>
*/
#[API\ApiProperty(writable: false)]
public array $updates;
}
@@ -0,0 +1,74 @@
<?php
namespace App\ApiResource\Project;
use ApiPlatform\Doctrine\Orm\Filter\OrderFilter;
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
use ApiPlatform\Doctrine\Orm\State\Options;
use ApiPlatform\Metadata as API;
use App\ApiResource\LocalizedApiResourceTrait;
use App\Entity\Project\Update;
use App\State\ApiResourceStateProcessor;
use App\State\ApiResourceStateProvider;
use Symfony\Component\Validator\Constraints as Assert;
/**
* A ProjectUpdate offers records of significant news during a Project's life.\
* \
* Updates can be for outstanding donation amounts, achievement of goals or thresholds,
* or any other body of information that the Project owner(s) may wish to make public to the Project's audience.
*/
#[API\ApiResource(
shortName: 'ProjectUpdate',
securityPostDenormalize: 'is_granted("PROJECT_EDIT", object.project)',
stateOptions: new Options(entityClass: Update::class),
provider: ApiResourceStateProvider::class,
processor: ApiResourceStateProcessor::class,
)]
class UpdateApiResource
{
use LocalizedApiResourceTrait;
#[API\ApiProperty(identifier: true, writable: false)]
public int $id;
/**
* The Project to which this update belongs to.
*/
#[Assert\NotBlank()]
#[API\ApiFilter(SearchFilter::class, strategy: 'exact')]
public ProjectApiResource $project;
/**
* Main headline for this update.
*/
#[Assert\NotBlank()]
public string $title;
/**
* Secondary headline for this update.
*/
#[Assert\NotBlank()]
public string $subtitle;
/**
* Main text body of the Project's update.
*/
#[Assert\NotBlank()]
public string $body;
/**
* Public display date for this update,
* not necessarily related to the actual dates of resource creation or update.
*/
#[Assert\DateTime()]
#[API\ApiProperty(default: 'now')]
#[API\ApiFilter(OrderFilter::class)]
public \DateTimeInterface $date;
/**
* URL to an image resource to be displayed as header.
*/
#[Assert\Url()]
public string $cover;
}
+37
View File
@@ -101,11 +101,18 @@ class Project implements UserOwnedInterface, AccountingOwnerInterface, Localized
#[ORM\OneToMany(targetEntity: BudgetItem::class, mappedBy: 'project')]
private Collection $budgetItems;
/**
* @var Collection<int, Update>
*/
#[ORM\OneToMany(targetEntity: Update::class, mappedBy: 'project')]
private Collection $updates;
public function __construct()
{
$this->accounting = Accounting::of($this);
$this->rewards = new ArrayCollection();
$this->budgetItems = new ArrayCollection();
$this->updates = new ArrayCollection();
}
public function getId(): ?int
@@ -263,4 +270,34 @@ class Project implements UserOwnedInterface, AccountingOwnerInterface, Localized
return $this;
}
/**
* @return Collection<int, Update>
*/
public function getUpdates(): Collection
{
return $this->updates;
}
public function addUpdate(Update $update): static
{
if (!$this->updates->contains($update)) {
$this->updates->add($update);
$update->setProject($this);
}
return $this;
}
public function removeUpdate(Update $update): static
{
if ($this->updates->removeElement($update)) {
// set the owning side to null (unless already changed)
if ($update->getProject() === $this) {
$update->setProject(null);
}
}
return $this;
}
}
+128
View File
@@ -0,0 +1,128 @@
<?php
namespace App\Entity\Project;
use App\Entity\Trait\LocalizedEntityTrait;
use App\Entity\Trait\TimestampedCreationEntity;
use App\Entity\Trait\TimestampedUpdationEntity;
use App\Mapping\Provider\EntityMapProvider;
use App\Repository\Project\UpdateRepository;
use AutoMapper\Attribute\MapProvider;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
#[MapProvider(EntityMapProvider::class)]
#[ORM\Table(name: 'project_update')]
#[ORM\Entity(repositoryClass: UpdateRepository::class)]
class Update
{
use LocalizedEntityTrait;
use TimestampedCreationEntity;
use TimestampedUpdationEntity;
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\ManyToOne(inversedBy: 'updates')]
#[ORM\JoinColumn(nullable: false)]
private ?Project $project = null;
#[ORM\Column(length: 255)]
private ?string $title = null;
#[ORM\Column(length: 255)]
private ?string $subtitle = null;
#[ORM\Column(type: Types::TEXT)]
private ?string $body = null;
#[ORM\Column(type: Types::DATETIME_MUTABLE)]
private ?\DateTimeInterface $date = null;
#[ORM\Column(type: Types::TEXT, nullable: true)]
private ?string $cover = null;
public function __construct()
{
$this->date = $this->date ?? new \DateTime();
}
public function getId(): ?int
{
return $this->id;
}
public function getProject(): ?Project
{
return $this->project;
}
public function setProject(?Project $project): static
{
$this->project = $project;
return $this;
}
public function getTitle(): ?string
{
return $this->title;
}
public function setTitle(string $title): static
{
$this->title = $title;
return $this;
}
public function getSubtitle(): ?string
{
return $this->subtitle;
}
public function setSubtitle(string $subtitle): static
{
$this->subtitle = $subtitle;
return $this;
}
public function getBody(): ?string
{
return $this->body;
}
public function setBody(string $body): static
{
$this->body = $body;
return $this;
}
public function getDate(): ?\DateTimeInterface
{
return $this->date;
}
public function setDate(\DateTimeInterface $date): static
{
$this->date = $date;
return $this;
}
public function getCover(): ?string
{
return $this->cover;
}
public function setCover(?string $cover): static
{
$this->cover = $cover;
return $this;
}
}
+23 -4
View File
@@ -21,19 +21,38 @@ trait RedocExtensionsTrait
$groups = [
[
'name' => 'Users',
'tags' => ['User', 'UserToken', 'Person', 'Organization'],
'tags' => [
'User',
'UserToken',
'Person',
'Organization',
],
],
[
'name' => 'Projects',
'tags' => ['Project', 'ProjectReward', 'ProjectRewardClaim', 'ProjectBudgetItem'],
'tags' => [
'Project',
'ProjectReward',
'ProjectRewardClaim',
'ProjectBudgetItem',
'ProjectUpdate',
],
],
[
'name' => 'Gateways',
'tags' => ['Gateway', 'GatewayCharge', 'GatewayCheckout'],
'tags' => [
'Gateway',
'GatewayCharge',
'GatewayCheckout',
],
],
[
'name' => 'Accounting',
'tags' => ['Accounting', 'AccountingBalancePoint', 'AccountingTransaction'],
'tags' => [
'Accounting',
'AccountingBalancePoint',
'AccountingTransaction',
],
],
];
@@ -0,0 +1,43 @@
<?php
namespace App\Repository\Project;
use App\Entity\Project\Update;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<Update>
*/
class UpdateRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Update::class);
}
// /**
// * @return Update[] Returns an array of Update objects
// */
// public function findByExampleField($value): array
// {
// return $this->createQueryBuilder('u')
// ->andWhere('u.exampleField = :val')
// ->setParameter('val', $value)
// ->orderBy('u.id', 'ASC')
// ->setMaxResults(10)
// ->getQuery()
// ->getResult()
// ;
// }
// public function findOneBySomeField($value): ?Update
// {
// return $this->createQueryBuilder('u')
// ->andWhere('u.exampleField = :val')
// ->setParameter('val', $value)
// ->getQuery()
// ->getOneOrNullResult()
// ;
// }
}