Merge pull request #71 from goteo/feat/project-updates
[feat] Project updates
This commit is contained in:
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
// ;
|
||||
// }
|
||||
}
|
||||
Reference in New Issue
Block a user