| @@ -54,13 +54,13 @@ class CommentApi | |||||
| #[NotBlank] | #[NotBlank] | ||||
| public ?string $message = null; | public ?string $message = null; | ||||
| #[IsValidOwner] | |||||
| #[ApiProperty(writable: false)] | |||||
| public ?UserApi $owner = null; | public ?UserApi $owner = null; | ||||
| #[ApiProperty(writable: false)] | #[ApiProperty(writable: false)] | ||||
| public ?string $ownerName = null; | public ?string $ownerName = null; | ||||
| public ?PostingApi $posting = null; | |||||
| public ?PostingApi $post = null; | |||||
| #[ApiProperty(writable: false)] | #[ApiProperty(writable: false)] | ||||
| public ?\DateTimeImmutable $createdAt = null; | public ?\DateTimeImmutable $createdAt = null; | ||||
| @@ -35,7 +35,7 @@ use Symfony\Component\Validator\Constraints\NotBlank; | |||||
| security: 'is_granted("ROLE_USER")', | security: 'is_granted("ROLE_USER")', | ||||
| ), | ), | ||||
| new Patch( | new Patch( | ||||
| security: 'is_granted("EDIT", object)', | |||||
| security: 'is_granted("ROLE_USER")', | |||||
| ), | ), | ||||
| new Delete( | new Delete( | ||||
| security: 'is_granted("ROLE_ADMIN")', | security: 'is_granted("ROLE_ADMIN")', | ||||
| @@ -59,6 +59,7 @@ class ContactApi | |||||
| #[NotBlank] | #[NotBlank] | ||||
| public ?string $lastName = null; | public ?string $lastName = null; | ||||
| #[NotBlank] | |||||
| public ?PartnerApi $partner = null; | public ?PartnerApi $partner = null; | ||||
| public ?\DateTimeInterface $birthday = null; | public ?\DateTimeInterface $birthday = null; | ||||
| @@ -79,7 +80,7 @@ class ContactApi | |||||
| * @var array<int, PostingApi> | * @var array<int, PostingApi> | ||||
| */ | */ | ||||
| #[ApiProperty(writable: false)] | #[ApiProperty(writable: false)] | ||||
| public array $postings = []; | |||||
| public array $posts = []; | |||||
| #[ApiProperty(writable: false)] | #[ApiProperty(writable: false)] | ||||
| public ?\DateTimeImmutable $createdAt = null; | public ?\DateTimeImmutable $createdAt = null; | ||||
| @@ -37,7 +37,7 @@ use Symfony\Component\Validator\Constraints\NotBlank; | |||||
| security: 'is_granted("ROLE_USER")', | security: 'is_granted("ROLE_USER")', | ||||
| ), | ), | ||||
| new Patch( | new Patch( | ||||
| security: 'is_granted("EDIT", object)', | |||||
| security: 'is_granted("ROLE_USER")', | |||||
| ), | ), | ||||
| new Delete( | new Delete( | ||||
| security: 'is_granted("ROLE_ADMIN")', | security: 'is_granted("ROLE_ADMIN")', | ||||
| @@ -84,5 +84,6 @@ class PartnerApi | |||||
| /** | /** | ||||
| * @var array<int, ContactApi> | * @var array<int, ContactApi> | ||||
| */ | */ | ||||
| #[ApiProperty(writable: false)] | |||||
| public array $contacts = []; | public array $contacts = []; | ||||
| } | } | ||||
| @@ -20,8 +20,9 @@ use ApiPlatform\Metadata\Get; | |||||
| use ApiPlatform\Metadata\GetCollection; | use ApiPlatform\Metadata\GetCollection; | ||||
| use ApiPlatform\Metadata\Patch; | use ApiPlatform\Metadata\Patch; | ||||
| use ApiPlatform\Metadata\Post; | use ApiPlatform\Metadata\Post; | ||||
| use App\Validator\IsValidOwner; | |||||
| use Symfony\Component\PropertyInfo\Type; | use Symfony\Component\PropertyInfo\Type; | ||||
| use Symfony\Component\Serializer\Attribute\Groups; | |||||
| use Symfony\Component\Validator\Constraints as Assert; | |||||
| use Symfony\Component\Validator\Constraints\NotBlank; | use Symfony\Component\Validator\Constraints\NotBlank; | ||||
| #[ApiResource( | #[ApiResource( | ||||
| @@ -34,9 +35,11 @@ use Symfony\Component\Validator\Constraints\NotBlank; | |||||
| security: 'is_granted("ROLE_USER")', | security: 'is_granted("ROLE_USER")', | ||||
| ), | ), | ||||
| new Post( | new Post( | ||||
| denormalizationContext: ['groups' => 'posting_create'], | |||||
| security: 'is_granted("ROLE_USER")', | security: 'is_granted("ROLE_USER")', | ||||
| ), | ), | ||||
| new Patch( | new Patch( | ||||
| denormalizationContext: ['groups' => 'posting_patch'], | |||||
| security: 'is_granted("EDIT", object)', | security: 'is_granted("EDIT", object)', | ||||
| ), | ), | ||||
| new Delete( | new Delete( | ||||
| @@ -55,28 +58,35 @@ class PostingApi | |||||
| public ?int $id = null; | public ?int $id = null; | ||||
| #[NotBlank] | #[NotBlank] | ||||
| #[Groups(['posting_create', 'posting_patch'])] | |||||
| public ?string $headline = null; | public ?string $headline = null; | ||||
| #[NotBlank] | #[NotBlank] | ||||
| #[Groups(['posting_create', 'posting_patch'])] | |||||
| public ?string $message = null; | public ?string $message = null; | ||||
| //#[IsValidOwner] | |||||
| #[ApiProperty(writable: false)] | #[ApiProperty(writable: false)] | ||||
| public ?UserApi $owner = null; | public ?UserApi $owner = null; | ||||
| #[ApiProperty(writable: false)] | #[ApiProperty(writable: false)] | ||||
| public ?string $ownerName = null; | public ?string $ownerName = null; | ||||
| #[ApiProperty(writable: true)] | |||||
| #[Groups(['posting_create'])] | |||||
| #[Assert\NotBlank(groups: ['posting_create'])] | |||||
| public ?PartnerApi $partner = null; | public ?PartnerApi $partner = null; | ||||
| #[ApiProperty(writable: true)] | |||||
| #[Groups(['posting_create'])] | |||||
| public ?ContactApi $contact = null; | public ?ContactApi $contact = null; | ||||
| /** | /** | ||||
| * @var array<int, CommentApi> | * @var array<int, CommentApi> | ||||
| */ | */ | ||||
| #[ApiProperty( | #[ApiProperty( | ||||
| writable: false, | |||||
| readableLink: true, | readableLink: true, | ||||
| writableLink: true, | |||||
| writableLink: false, | |||||
| fetchEager: true, | fetchEager: true, | ||||
| builtinTypes: [ | builtinTypes: [ | ||||
| new Type( | new Type( | ||||
| @@ -30,10 +30,10 @@ use Symfony\Component\Validator\Constraints as Assert; | |||||
| ), | ), | ||||
| new Post( | new Post( | ||||
| security: 'is_granted("ROLE_ADMIN")', | security: 'is_granted("ROLE_ADMIN")', | ||||
| validationContext: ['groups' => ['Default', 'postValidation']], | |||||
| validationContext: ['groups' => ['Default', 'postValidation']] | |||||
| ), | ), | ||||
| new Patch( | new Patch( | ||||
| security: 'is_granted("ROLE_USER")' | |||||
| security: 'is_granted("is_granted("EDIT", object)")' | |||||
| ), | ), | ||||
| ], | ], | ||||
| security: 'is_granted("ROLE_USER")', | security: 'is_granted("ROLE_USER")', | ||||
| @@ -74,7 +74,8 @@ class UserApi | |||||
| /** | /** | ||||
| * @var array<int, PostingApi> | * @var array<int, PostingApi> | ||||
| */ | */ | ||||
| public array $postings = []; | |||||
| #[ApiProperty(writable: false)] | |||||
| public array $posts = []; | |||||
| #[ApiProperty(writable: false)] | #[ApiProperty(writable: false)] | ||||
| public ?\DateTimeImmutable $createdAt = null; | public ?\DateTimeImmutable $createdAt = null; | ||||
| @@ -28,8 +28,10 @@ class Comment | |||||
| #[ORM\Column] | #[ORM\Column] | ||||
| private ?\DateTimeImmutable $createdAt = null; | private ?\DateTimeImmutable $createdAt = null; | ||||
| public function __construct() | |||||
| public function __construct(User $owner, Posting $posting) | |||||
| { | { | ||||
| $this->owner = $owner; | |||||
| $this->posting = $posting; | |||||
| $this->createdAt = new \DateTimeImmutable(); | $this->createdAt = new \DateTimeImmutable(); | ||||
| } | } | ||||
| @@ -62,25 +64,11 @@ class Comment | |||||
| return $this->owner; | return $this->owner; | ||||
| } | } | ||||
| public function setOwner(?User $owner): static | |||||
| { | |||||
| $this->owner = $owner; | |||||
| return $this; | |||||
| } | |||||
| public function getPosting(): ?Posting | public function getPosting(): ?Posting | ||||
| { | { | ||||
| return $this->posting; | return $this->posting; | ||||
| } | } | ||||
| public function setPosting(?Posting $posting): static | |||||
| { | |||||
| $this->posting = $posting; | |||||
| return $this; | |||||
| } | |||||
| public function getCreatedAt(): ?\DateTimeImmutable | public function getCreatedAt(): ?\DateTimeImmutable | ||||
| { | { | ||||
| return $this->createdAt; | return $this->createdAt; | ||||
| @@ -46,13 +46,13 @@ class Contact | |||||
| private ?\DateTimeImmutable $createdAt = null; | private ?\DateTimeImmutable $createdAt = null; | ||||
| #[ORM\OneToMany(mappedBy: 'contact', targetEntity: Posting::class, orphanRemoval: true)] | #[ORM\OneToMany(mappedBy: 'contact', targetEntity: Posting::class, orphanRemoval: true)] | ||||
| private Collection $postings; | |||||
| private Collection $posts; | |||||
| public function __construct(Partner $partner) | public function __construct(Partner $partner) | ||||
| { | { | ||||
| $this->partner = $partner; | $this->partner = $partner; | ||||
| $this->createdAt = new \DateTimeImmutable(); | $this->createdAt = new \DateTimeImmutable(); | ||||
| $this->postings = new ArrayCollection(); | |||||
| $this->posts = new ArrayCollection(); | |||||
| } | } | ||||
| public function getId(): ?int | public function getId(): ?int | ||||
| @@ -169,15 +169,15 @@ class Contact | |||||
| /** | /** | ||||
| * @return Collection<int, Posting> | * @return Collection<int, Posting> | ||||
| */ | */ | ||||
| public function getPostings(): Collection | |||||
| public function getPosts(): Collection | |||||
| { | { | ||||
| return $this->postings; | |||||
| return $this->posts; | |||||
| } | } | ||||
| public function addPosting(Posting $posting): static | public function addPosting(Posting $posting): static | ||||
| { | { | ||||
| if (!$this->postings->contains($posting)) { | |||||
| $this->postings->add($posting); | |||||
| if (!$this->posts->contains($posting)) { | |||||
| $this->posts->add($posting); | |||||
| $posting->setContact($this); | $posting->setContact($this); | ||||
| } | } | ||||
| @@ -186,7 +186,7 @@ class Contact | |||||
| public function removePosting(Posting $posting): static | public function removePosting(Posting $posting): static | ||||
| { | { | ||||
| if ($this->postings->removeElement($posting)) { | |||||
| if ($this->posts->removeElement($posting)) { | |||||
| // set the owning side to null (unless already changed) | // set the owning side to null (unless already changed) | ||||
| if ($posting->getContact() === $this) { | if ($posting->getContact() === $this) { | ||||
| $posting->setContact(null); | $posting->setContact(null); | ||||
| @@ -23,6 +23,7 @@ use Vich\UploaderBundle\Mapping\Annotation as Vich; | |||||
| #[Vich\Uploadable] | #[Vich\Uploadable] | ||||
| #[ORM\Entity] | #[ORM\Entity] | ||||
| #[ApiResource( | #[ApiResource( | ||||
| shortName: 'Media', | |||||
| types: ['https://schema.org/MediaObject'], | types: ['https://schema.org/MediaObject'], | ||||
| operations: [ | operations: [ | ||||
| new Get(), | new Get(), | ||||
| @@ -70,6 +70,11 @@ class Posting | |||||
| return $this->createdAt; | return $this->createdAt; | ||||
| } | } | ||||
| public function setOwner(?User $owner): void | |||||
| { | |||||
| $this->owner = $owner; | |||||
| } | |||||
| public function getOwner(): ?User | public function getOwner(): ?User | ||||
| { | { | ||||
| return $this->owner; | return $this->owner; | ||||
| @@ -38,7 +38,7 @@ class AuthenticationSuccessListener | |||||
| } | } | ||||
| $userApi = $this->microMapper->map($user, UserApi::class, [ | $userApi = $this->microMapper->map($user, UserApi::class, [ | ||||
| MicroMapperInterface::MAX_DEPTH => 3, | |||||
| MicroMapperInterface::MAX_DEPTH => 1, | |||||
| ]); | ]); | ||||
| $data['id'] = $this->iriConverter->getIriFromResource($userApi); | $data['id'] = $this->iriConverter->getIriFromResource($userApi); | ||||
| @@ -46,7 +46,7 @@ final class PostingFactory extends ModelFactory | |||||
| */ | */ | ||||
| protected function getDefaults(): array | protected function getDefaults(): array | ||||
| { | { | ||||
| $randomBoolean = (bool)random_int(0, 1); | |||||
| $randomBoolean = ContactFactory::count() > 0 && (bool)random_int(0, 1); | |||||
| return [ | return [ | ||||
| 'headline' => self::faker()->words(random_int(1, 5), true), | 'headline' => self::faker()->words(random_int(1, 5), true), | ||||
| 'message' => $randomBoolean ? self::faker()->sentence() : self::faker()->sentences(random_int(1, 3), true), | 'message' => $randomBoolean ? self::faker()->sentence() : self::faker()->sentences(random_int(1, 3), true), | ||||
| @@ -4,6 +4,7 @@ namespace App\Mapper; | |||||
| use App\ApiResource\CommentApi; | use App\ApiResource\CommentApi; | ||||
| use App\Entity\Comment; | use App\Entity\Comment; | ||||
| use App\Entity\Posting; | |||||
| use App\Entity\User; | use App\Entity\User; | ||||
| use App\Repository\CommentRepository; | use App\Repository\CommentRepository; | ||||
| use Symfony\Bundle\SecurityBundle\Security; | use Symfony\Bundle\SecurityBundle\Security; | ||||
| @@ -28,7 +29,21 @@ class CommentApiToEntityMapper implements MapperInterface | |||||
| $dto = $from; | $dto = $from; | ||||
| assert($dto instanceof CommentApi); | assert($dto instanceof CommentApi); | ||||
| $entity = $dto->id ? $this->repository->find($dto->id) : new Comment(); | |||||
| if ($dto->id) { | |||||
| $entity = $this->repository->find($dto->id); | |||||
| } else { | |||||
| $user = $this->security->getUser(); | |||||
| assert($user instanceof User); | |||||
| if ($dto->post === null) { | |||||
| throw new \Exception('Posting missing'); | |||||
| } | |||||
| $posting = $this->microMapper->map($dto->post, Posting::class, [ | |||||
| MicroMapperInterface::MAX_DEPTH => 1, | |||||
| ]); | |||||
| assert($posting instanceof Posting); | |||||
| $entity = new Comment($user, $posting); | |||||
| } | |||||
| if (!$entity) { | if (!$entity) { | ||||
| throw new \Exception('Comment not found'); | throw new \Exception('Comment not found'); | ||||
| } | } | ||||
| @@ -42,15 +57,6 @@ class CommentApiToEntityMapper implements MapperInterface | |||||
| assert($dto instanceof CommentApi); | assert($dto instanceof CommentApi); | ||||
| $entity = $to; | $entity = $to; | ||||
| assert($entity instanceof Comment); | assert($entity instanceof Comment); | ||||
| if ($dto->owner) { | |||||
| $entity->setOwner($this->microMapper->map($dto->owner, User::class, [ | |||||
| MicroMapperInterface::MAX_DEPTH => 1, | |||||
| ])); | |||||
| } else { | |||||
| $entity->setOwner($this->security->getUser()); | |||||
| } | |||||
| $entity->setPosting($dto->posting); | |||||
| $entity->setMessage($dto->message); | $entity->setMessage($dto->message); | ||||
| return $entity; | return $entity; | ||||
| @@ -15,8 +15,7 @@ use Symfonycasts\MicroMapper\MicroMapperInterface; | |||||
| class CommentEntityToApiMapper implements MapperInterface | class CommentEntityToApiMapper implements MapperInterface | ||||
| { | { | ||||
| public function __construct( | public function __construct( | ||||
| private MicroMapperInterface $microMapper, | |||||
| private Security $security, | |||||
| private MicroMapperInterface $microMapper | |||||
| ) | ) | ||||
| { | { | ||||
| } | } | ||||
| @@ -41,12 +40,12 @@ class CommentEntityToApiMapper implements MapperInterface | |||||
| $dto->message = $entity->getMessage(); | $dto->message = $entity->getMessage(); | ||||
| $dto->owner = $this->microMapper->map($entity->getOwner(), UserApi::class, [ | $dto->owner = $this->microMapper->map($entity->getOwner(), UserApi::class, [ | ||||
| MicroMapperInterface::MAX_DEPTH => 0, | |||||
| MicroMapperInterface::MAX_DEPTH => 1, | |||||
| ]); | ]); | ||||
| $dto->ownerName = $entity->getOwner()?->getFirstName()." ".$entity->getOwner()?->getLastName(); | $dto->ownerName = $entity->getOwner()?->getFirstName()." ".$entity->getOwner()?->getLastName(); | ||||
| $dto->posting = $this->microMapper->map($entity->getPosting(), PostingApi::class, [ | $dto->posting = $this->microMapper->map($entity->getPosting(), PostingApi::class, [ | ||||
| MicroMapperInterface::MAX_DEPTH => 0, | |||||
| MicroMapperInterface::MAX_DEPTH => 1, | |||||
| ]); | ]); | ||||
| $dto->createdAt = $entity->getCreatedAt(); | $dto->createdAt = $entity->getCreatedAt(); | ||||
| @@ -17,7 +17,6 @@ class ContactApiToEntityMapper implements MapperInterface | |||||
| { | { | ||||
| public function __construct( | public function __construct( | ||||
| private ContactRepository $repository, | private ContactRepository $repository, | ||||
| private Security $security, | |||||
| private MicroMapperInterface $microMapper, | private MicroMapperInterface $microMapper, | ||||
| ) | ) | ||||
| { | { | ||||
| @@ -36,7 +35,7 @@ class ContactApiToEntityMapper implements MapperInterface | |||||
| throw new \Exception('Partner missing'); | throw new \Exception('Partner missing'); | ||||
| } | } | ||||
| $partner = $this->microMapper->map($dto->partner, Partner::class, [ | $partner = $this->microMapper->map($dto->partner, Partner::class, [ | ||||
| MicroMapperInterface::MAX_DEPTH => 0, | |||||
| MicroMapperInterface::MAX_DEPTH => 1, | |||||
| ]); | ]); | ||||
| $entity = new Contact($partner); | $entity = new Contact($partner); | ||||
| } | } | ||||
| @@ -58,7 +57,6 @@ class ContactApiToEntityMapper implements MapperInterface | |||||
| $entity->setFirstName($dto->firstName); | $entity->setFirstName($dto->firstName); | ||||
| $entity->setLastName($dto->lastName); | $entity->setLastName($dto->lastName); | ||||
| $entity->setPartner($dto->partner); | |||||
| $entity->setBirthday($dto->birthday); | $entity->setBirthday($dto->birthday); | ||||
| $entity->setImage($dto->image); | $entity->setImage($dto->image); | ||||
| $entity->setPosition($dto->position); | $entity->setPosition($dto->position); | ||||
| @@ -44,7 +44,7 @@ class ContactEntityToApiMapper implements MapperInterface | |||||
| $dto->lastName = $entity->getLastName(); | $dto->lastName = $entity->getLastName(); | ||||
| $dto->partner = $this->microMapper->map( | $dto->partner = $this->microMapper->map( | ||||
| $entity->getPartner(), PartnerApi::class, [ | $entity->getPartner(), PartnerApi::class, [ | ||||
| MicroMapperInterface::MAX_DEPTH => 0, | |||||
| MicroMapperInterface::MAX_DEPTH => 1, | |||||
| ] | ] | ||||
| ); | ); | ||||
| $dto->birthday = $entity->getBirthday(); | $dto->birthday = $entity->getBirthday(); | ||||
| @@ -55,11 +55,11 @@ class ContactEntityToApiMapper implements MapperInterface | |||||
| $dto->email = $entity->getEmail(); | $dto->email = $entity->getEmail(); | ||||
| $dto->createdAt = $entity->getCreatedAt(); | $dto->createdAt = $entity->getCreatedAt(); | ||||
| $dto->postings = array_map(function(Posting $posting) { | |||||
| $dto->posts = array_map(function(Posting $posting) { | |||||
| return $this->microMapper->map($posting, PostingApi::class, [ | return $this->microMapper->map($posting, PostingApi::class, [ | ||||
| MicroMapperInterface::MAX_DEPTH => 0, | |||||
| MicroMapperInterface::MAX_DEPTH => 1, | |||||
| ]); | ]); | ||||
| }, $entity->getPostings()->getValues()); | |||||
| }, $entity->getPosts()->getValues()); | |||||
| return $dto; | return $dto; | ||||
| } | } | ||||
| @@ -19,10 +19,7 @@ use Symfonycasts\MicroMapper\MicroMapperInterface; | |||||
| class PartnerApiToEntityMapper implements MapperInterface | class PartnerApiToEntityMapper implements MapperInterface | ||||
| { | { | ||||
| public function __construct( | public function __construct( | ||||
| private PartnerRepository $repository, | |||||
| private Security $security, | |||||
| private MicroMapperInterface $microMapper, | |||||
| private PropertyAccessorInterface $propertyAccessor, | |||||
| private PartnerRepository $repository | |||||
| ) | ) | ||||
| { | { | ||||
| @@ -58,14 +55,6 @@ class PartnerApiToEntityMapper implements MapperInterface | |||||
| $entity->setWebsite($dto->website); | $entity->setWebsite($dto->website); | ||||
| $entity->setLogo($dto->logo); | $entity->setLogo($dto->logo); | ||||
| $contactEntities = []; | |||||
| foreach ($dto->contacts as $contactApi) { | |||||
| $contactEntities[] = $this->microMapper->map($contactApi, Contact::class, [ | |||||
| MicroMapperInterface::MAX_DEPTH => 0, | |||||
| ]); | |||||
| } | |||||
| $this->propertyAccessor->setValue($entity, 'contacts', $contactEntities); | |||||
| return $entity; | return $entity; | ||||
| } | } | ||||
| } | } | ||||
| @@ -52,7 +52,7 @@ class PartnerEntityToApiMapper implements MapperInterface | |||||
| $dto->createdAt = $entity->getCreatedAt(); | $dto->createdAt = $entity->getCreatedAt(); | ||||
| $dto->contacts = array_map(function(Contact $contact) { | $dto->contacts = array_map(function(Contact $contact) { | ||||
| return $this->microMapper->map($contact, ContactApi::class, [ | return $this->microMapper->map($contact, ContactApi::class, [ | ||||
| MicroMapperInterface::MAX_DEPTH => 0, | |||||
| MicroMapperInterface::MAX_DEPTH => 1, | |||||
| ]); | ]); | ||||
| }, $entity->getContacts()->getValues()); | }, $entity->getContacts()->getValues()); | ||||
| return $dto; | return $dto; | ||||
| @@ -40,13 +40,13 @@ class PostingApiToEntityMapper implements MapperInterface | |||||
| throw new \Exception('Partner missing'); | throw new \Exception('Partner missing'); | ||||
| } | } | ||||
| $partner = $this->microMapper->map($dto->partner, Partner::class, [ | $partner = $this->microMapper->map($dto->partner, Partner::class, [ | ||||
| MicroMapperInterface::MAX_DEPTH => 0, | |||||
| MicroMapperInterface::MAX_DEPTH => 1, | |||||
| ]); | ]); | ||||
| $contact = null; | $contact = null; | ||||
| if ($dto->contact) { | if ($dto->contact) { | ||||
| assert($dto->contact instanceof ContactApi); | assert($dto->contact instanceof ContactApi); | ||||
| $contact = $this->microMapper->map($dto->contact, Contact::class, [ | $contact = $this->microMapper->map($dto->contact, Contact::class, [ | ||||
| MicroMapperInterface::MAX_DEPTH => 0, | |||||
| MicroMapperInterface::MAX_DEPTH => 1, | |||||
| ]); | ]); | ||||
| } | } | ||||
| assert($partner instanceof Partner); | assert($partner instanceof Partner); | ||||
| @@ -53,9 +53,9 @@ class UserApiToEntityMapper implements MapperInterface | |||||
| } | } | ||||
| $userPostsEntities = new ArrayCollection(); | $userPostsEntities = new ArrayCollection(); | ||||
| foreach ($dto->postings as $userPostApi) { | |||||
| foreach ($dto->posts as $userPostApi) { | |||||
| $userPostsEntities[] = $this->microMapper->map($userPostApi, Posting::class, [ | $userPostsEntities[] = $this->microMapper->map($userPostApi, Posting::class, [ | ||||
| MicroMapperInterface::MAX_DEPTH => 0, | |||||
| MicroMapperInterface::MAX_DEPTH => 1, | |||||
| ]); | ]); | ||||
| } | } | ||||
| $this->propertyAccessor->setValue($entity, 'postings', $userPostsEntities); | $this->propertyAccessor->setValue($entity, 'postings', $userPostsEntities); | ||||
| @@ -41,9 +41,9 @@ class UserEntityToApiMapper implements MapperInterface | |||||
| $dto->firstName = $entity->getFirstName(); | $dto->firstName = $entity->getFirstName(); | ||||
| $dto->lastName = $entity->getLastName(); | $dto->lastName = $entity->getLastName(); | ||||
| $dto->postings = array_map(function(Posting $posting) { | |||||
| $dto->posts = array_map(function(Posting $posting) { | |||||
| return $this->microMapper->map($posting, PostingApi::class, [ | return $this->microMapper->map($posting, PostingApi::class, [ | ||||
| MicroMapperInterface::MAX_DEPTH => 0, | |||||
| MicroMapperInterface::MAX_DEPTH => 1, | |||||
| ]); | ]); | ||||
| }, $entity->getPostings()->getValues()); | }, $entity->getPostings()->getValues()); | ||||
| @@ -0,0 +1,46 @@ | |||||
| <?php | |||||
| namespace App\Voter; | |||||
| use App\ApiResource\CommentApi; | |||||
| use App\ApiResource\PostingApi; | |||||
| use App\Entity\User; | |||||
| use Symfony\Bundle\SecurityBundle\Security; | |||||
| use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; | |||||
| use Symfony\Component\Security\Core\Authorization\Voter\Voter; | |||||
| class CommentApiVoter extends Voter | |||||
| { | |||||
| public const EDIT = 'EDIT'; | |||||
| public function __construct() | |||||
| { | |||||
| } | |||||
| protected function supports(string $attribute, mixed $subject): bool | |||||
| { | |||||
| return $attribute === self::EDIT && $subject instanceof CommentApi; | |||||
| } | |||||
| protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool | |||||
| { | |||||
| $user = $token->getUser(); | |||||
| // if the user is anonymous, do not grant access | |||||
| if (!$user instanceof User) { | |||||
| return false; | |||||
| } | |||||
| assert($subject instanceof CommentApi); | |||||
| // ... (check conditions and return true to grant permission) ... | |||||
| switch ($attribute) { | |||||
| case self::EDIT: | |||||
| if ($subject->owner?->id === $user->getId()) { | |||||
| return true; | |||||
| } | |||||
| break; | |||||
| } | |||||
| return false; | |||||
| } | |||||
| } | |||||
| @@ -12,13 +12,13 @@ class PostingApiVoter extends Voter | |||||
| { | { | ||||
| public const EDIT = 'EDIT'; | public const EDIT = 'EDIT'; | ||||
| public function __construct(private Security $security) | |||||
| public function __construct() | |||||
| { | { | ||||
| } | } | ||||
| protected function supports(string $attribute, mixed $subject): bool | protected function supports(string $attribute, mixed $subject): bool | ||||
| { | { | ||||
| return in_array($attribute, [self::EDIT]) && $subject instanceof PostingApi; | |||||
| return $attribute === self::EDIT && $subject instanceof PostingApi; | |||||
| } | } | ||||
| protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool | protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool | ||||
| @@ -29,10 +29,6 @@ class PostingApiVoter extends Voter | |||||
| return false; | return false; | ||||
| } | } | ||||
| if ($this->security->isGranted('ROLE_ADMIN')) { | |||||
| return true; | |||||
| } | |||||
| assert($subject instanceof PostingApi); | assert($subject instanceof PostingApi); | ||||
| // ... (check conditions and return true to grant permission) ... | // ... (check conditions and return true to grant permission) ... | ||||
| @@ -18,12 +18,11 @@ class UserApiVoter extends Voter | |||||
| protected function supports(string $attribute, mixed $subject): bool | protected function supports(string $attribute, mixed $subject): bool | ||||
| { | { | ||||
| return in_array($attribute, [self::EDIT]) && $subject instanceof UserApi; | |||||
| return $attribute === self::EDIT && $subject instanceof UserApi; | |||||
| } | } | ||||
| protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool | protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool | ||||
| { | { | ||||
| //dd($subject); | |||||
| $user = $token->getUser(); | $user = $token->getUser(); | ||||
| // if the user is anonymous, do not grant access | // if the user is anonymous, do not grant access | ||||
| if (!$user instanceof User) { | if (!$user instanceof User) { | ||||
| @@ -0,0 +1,78 @@ | |||||
| <?php | |||||
| /** | |||||
| * @author Daniel Knudsen <d.knudsen@spawntree.de> | |||||
| * @date 12.12.23 | |||||
| */ | |||||
| namespace App\Tests\Functional; | |||||
| use App\Enum\PartnerType; | |||||
| use App\Factory\CommentFactory; | |||||
| use App\Factory\MediaObjectLogoFactory; | |||||
| use App\Factory\MediaObjectProfileFactory; | |||||
| use App\Factory\PartnerFactory; | |||||
| use App\Factory\PostingFactory; | |||||
| use App\Factory\UserFactory; | |||||
| use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface; | |||||
| use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; | |||||
| use Zenstruck\Browser\Test\HasBrowser; | |||||
| use Zenstruck\Foundry\Test\Factories; | |||||
| use Zenstruck\Foundry\Test\ResetDatabase; | |||||
| class CommentResourceTest extends KernelTestCase | |||||
| { | |||||
| use HasBrowser; | |||||
| use ResetDatabase; | |||||
| use Factories; | |||||
| private JWTTokenManagerInterface $JWTManager; | |||||
| protected function setUp(): void | |||||
| { | |||||
| parent::setUp(); | |||||
| $this->JWTManager = self::getContainer()->get('lexik_jwt_authentication.jwt_manager'); | |||||
| } | |||||
| public function testPostComment(): void | |||||
| { | |||||
| $user = UserFactory::createOne( | |||||
| [ | |||||
| 'firstName' => 'Peter', | |||||
| 'lastName' => 'Test', | |||||
| 'password' => 'test', | |||||
| 'email' => 'peter@test.de', | |||||
| ] | |||||
| ); | |||||
| MediaObjectLogoFactory::createOne(); | |||||
| PartnerFactory::createOne(); | |||||
| $posting = PostingFactory::createOne(); | |||||
| CommentFactory::createOne(); | |||||
| $token = $this->JWTManager->create($user->object()); | |||||
| $this->browser() | |||||
| ->post('/api/comments' , [ | |||||
| 'json' => [ | |||||
| 'message' => 'my comment', | |||||
| 'post' => '/api/posts/' . $posting->getId(), | |||||
| ], | |||||
| 'headers' => [ | |||||
| 'Authorization' => 'Bearer ' . $token, | |||||
| ] | |||||
| ]) | |||||
| ->assertSuccessful() | |||||
| ; | |||||
| $this->browser() | |||||
| ->get('/api/comments', [ | |||||
| 'headers' => [ | |||||
| 'Authorization' => 'Bearer ' . $token, | |||||
| ], | |||||
| ]) | |||||
| ->assertSuccessful() | |||||
| ->assertJsonMatches('"hydra:totalItems"', 2) | |||||
| ->assertJsonMatches('"hydra:member"[1].message', 'my comment') | |||||
| ; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,82 @@ | |||||
| <?php | |||||
| /** | |||||
| * @author Daniel Knudsen <d.knudsen@spawntree.de> | |||||
| * @date 12.12.23 | |||||
| */ | |||||
| namespace App\Tests\Functional; | |||||
| use App\Enum\PartnerType; | |||||
| use App\Factory\MediaObjectLogoFactory; | |||||
| use App\Factory\MediaObjectProfileFactory; | |||||
| use App\Factory\PartnerFactory; | |||||
| use App\Factory\UserFactory; | |||||
| use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface; | |||||
| use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; | |||||
| use Zenstruck\Browser\Test\HasBrowser; | |||||
| use Zenstruck\Foundry\Test\Factories; | |||||
| use Zenstruck\Foundry\Test\ResetDatabase; | |||||
| class ContactResourceTest extends KernelTestCase | |||||
| { | |||||
| use HasBrowser; | |||||
| use ResetDatabase; | |||||
| use Factories; | |||||
| private JWTTokenManagerInterface $JWTManager; | |||||
| protected function setUp(): void | |||||
| { | |||||
| parent::setUp(); | |||||
| $this->JWTManager = self::getContainer()->get('lexik_jwt_authentication.jwt_manager'); | |||||
| } | |||||
| public function testPostContact(): void | |||||
| { | |||||
| MediaObjectLogoFactory::createOne(); | |||||
| $partner = PartnerFactory::createOne(); | |||||
| $user = UserFactory::createOne( | |||||
| [ | |||||
| 'firstName' => 'Peter', | |||||
| 'lastName' => 'Test', | |||||
| 'password' => 'test', | |||||
| 'email' => 'peter@test.de', | |||||
| ] | |||||
| ); | |||||
| $mediaObject = MediaObjectProfileFactory::createOne(); | |||||
| $token = $this->JWTManager->create($user->object()); | |||||
| $this->browser() | |||||
| ->post('/api/contacts' , [ | |||||
| 'json' => [ | |||||
| 'firstName' => 'Peter', | |||||
| 'lastName' => 'Test', | |||||
| 'partner' => '/api/partners/' . $partner->getId(), | |||||
| 'birthday' => '1984-02-10', | |||||
| 'image' => '/api/medias/' . $mediaObject->getId(), | |||||
| 'position' => 'CEO', | |||||
| 'phone' => '123456789', | |||||
| 'email' => 'peter@test2.de', | |||||
| ], | |||||
| 'headers' => [ | |||||
| 'Authorization' => 'Bearer ' . $token, | |||||
| ] | |||||
| ]) | |||||
| ->assertSuccessful() | |||||
| ; | |||||
| $this->browser() | |||||
| ->get('/api/contacts', [ | |||||
| 'headers' => [ | |||||
| 'Authorization' => 'Bearer ' . $token, | |||||
| ], | |||||
| ]) | |||||
| ->assertSuccessful() | |||||
| ->assertJsonMatches('"hydra:totalItems"', 1) | |||||
| ->assertJsonMatches('"hydra:member"[0].position', 'CEO') | |||||
| ; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,66 @@ | |||||
| <?php | |||||
| /** | |||||
| * @author Daniel Knudsen <d.knudsen@spawntree.de> | |||||
| * @date 01.03.24 | |||||
| */ | |||||
| namespace App\Tests\Functional; | |||||
| use App\Factory\UserFactory; | |||||
| use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface; | |||||
| use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; | |||||
| use Symfony\Component\HttpFoundation\File\UploadedFile; | |||||
| use Zenstruck\Browser\Test\HasBrowser; | |||||
| use Zenstruck\Foundry\Test\Factories; | |||||
| use Zenstruck\Foundry\Test\ResetDatabase; | |||||
| class MediaObjectResourceTest extends KernelTestCase | |||||
| { | |||||
| use HasBrowser; | |||||
| use ResetDatabase; | |||||
| use Factories; | |||||
| private JWTTokenManagerInterface $JWTManager; | |||||
| private string $projectDir; | |||||
| protected function setUp(): void | |||||
| { | |||||
| parent::setUp(); | |||||
| $this->JWTManager = self::getContainer()->get('lexik_jwt_authentication.jwt_manager'); | |||||
| $this->projectDir = self::getContainer()->get('kernel')->getProjectDir(); | |||||
| } | |||||
| public function testCreateAMediaObject(): void | |||||
| { | |||||
| $path = $this->projectDir . '/tests/fixtures/'; | |||||
| $srcFile = $path . '1176.png'; | |||||
| $dstFile = $path . '1176_upload.png'; | |||||
| copy($srcFile, $dstFile); | |||||
| $file = new UploadedFile($dstFile, 'image.png'); | |||||
| $user = UserFactory::createOne( | |||||
| [ | |||||
| 'firstName' => 'Peter', | |||||
| 'lastName' => 'Test', | |||||
| 'password' => 'test', | |||||
| 'email' => 'peter@test.de', | |||||
| ] | |||||
| ); | |||||
| $token = $this->JWTManager->create($user->object()); | |||||
| $this->browser() | |||||
| ->post('/api/medias', [ | |||||
| 'headers' => [ | |||||
| 'Authorization' => 'Bearer ' . $token, | |||||
| 'Content-Type' => 'multipart/form-data' | |||||
| ], | |||||
| 'files' => [ | |||||
| 'file' => $file, | |||||
| ], | |||||
| ]) | |||||
| ->assertSuccessful() | |||||
| ; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,79 @@ | |||||
| <?php | |||||
| /** | |||||
| * @author Daniel Knudsen <d.knudsen@spawntree.de> | |||||
| * @date 12.12.23 | |||||
| */ | |||||
| namespace App\Tests\Functional; | |||||
| use App\Enum\PartnerType; | |||||
| use App\Factory\MediaObjectLogoFactory; | |||||
| use App\Factory\UserFactory; | |||||
| use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface; | |||||
| use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; | |||||
| use Zenstruck\Browser\Test\HasBrowser; | |||||
| use Zenstruck\Foundry\Test\Factories; | |||||
| use Zenstruck\Foundry\Test\ResetDatabase; | |||||
| class PartnerResourceTest extends KernelTestCase | |||||
| { | |||||
| use HasBrowser; | |||||
| use ResetDatabase; | |||||
| use Factories; | |||||
| private JWTTokenManagerInterface $JWTManager; | |||||
| protected function setUp(): void | |||||
| { | |||||
| parent::setUp(); | |||||
| $this->JWTManager = self::getContainer()->get('lexik_jwt_authentication.jwt_manager'); | |||||
| } | |||||
| public function testPostPartner(): void | |||||
| { | |||||
| $user = UserFactory::createOne( | |||||
| [ | |||||
| 'email' => 'peter@test.de', | |||||
| 'firstName' => 'Peter', | |||||
| 'lastName' => 'Test', | |||||
| 'password' => 'test', | |||||
| ] | |||||
| ); | |||||
| $mediaObject = MediaObjectLogoFactory::createOne(); | |||||
| $token = $this->JWTManager->create($user->object()); | |||||
| $this->browser() | |||||
| ->post('/api/partners' , [ | |||||
| 'json' => [ | |||||
| 'name' => 'test customer', | |||||
| 'type' => PartnerType::Customer, | |||||
| 'street' => 'test street', | |||||
| 'streetNo' => '11', | |||||
| 'zip' => '22335', | |||||
| 'city' => 'test city', | |||||
| 'country' => 'test country', | |||||
| 'website' => 'wwe.test.de', | |||||
| 'logo' => '/api/medias/' . $mediaObject->getId(), | |||||
| ], | |||||
| 'headers' => [ | |||||
| 'Authorization' => 'Bearer ' . $token, | |||||
| 'Content-Type' => 'application/ld+json' | |||||
| ] | |||||
| ]) | |||||
| ->assertSuccessful() | |||||
| ; | |||||
| $this->browser() | |||||
| ->get('/api/partners', [ | |||||
| 'headers' => [ | |||||
| 'Authorization' => 'Bearer ' . $token, | |||||
| ], | |||||
| ]) | |||||
| ->assertSuccessful() | |||||
| ->assertJsonMatches('"hydra:totalItems"', 1) | |||||
| ->assertJsonMatches('"hydra:member"[0].name', 'test customer') | |||||
| ; | |||||
| } | |||||
| } | |||||
| @@ -13,6 +13,7 @@ use App\Factory\MediaObjectProfileFactory; | |||||
| use App\Factory\PartnerFactory; | use App\Factory\PartnerFactory; | ||||
| use App\Factory\PostingFactory; | use App\Factory\PostingFactory; | ||||
| use App\Factory\UserFactory; | use App\Factory\UserFactory; | ||||
| use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface; | |||||
| use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; | use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; | ||||
| use Zenstruck\Browser\Test\HasBrowser; | use Zenstruck\Browser\Test\HasBrowser; | ||||
| use Zenstruck\Foundry\Test\Factories; | use Zenstruck\Foundry\Test\Factories; | ||||
| @@ -24,9 +25,17 @@ class PostingResourceTest extends KernelTestCase | |||||
| use ResetDatabase; | use ResetDatabase; | ||||
| use Factories; | use Factories; | ||||
| private JWTTokenManagerInterface $JWTManager; | |||||
| protected function setUp(): void | |||||
| { | |||||
| parent::setUp(); | |||||
| $this->JWTManager = self::getContainer()->get('lexik_jwt_authentication.jwt_manager'); | |||||
| } | |||||
| public function testPostPosting(): void | public function testPostPosting(): void | ||||
| { | { | ||||
| UserFactory::createOne( | |||||
| $user = UserFactory::createOne( | |||||
| [ | [ | ||||
| 'email' => 'peter@test.de', | 'email' => 'peter@test.de', | ||||
| 'firstName' => 'Peter', | 'firstName' => 'Peter', | ||||
| @@ -41,17 +50,7 @@ class PostingResourceTest extends KernelTestCase | |||||
| $contact = ContactFactory::createOne(); | $contact = ContactFactory::createOne(); | ||||
| PostingFactory::createOne(); | PostingFactory::createOne(); | ||||
| $response = $this->browser() | |||||
| ->post('/auth', [ | |||||
| 'json' => [ | |||||
| 'email' => 'peter@test.de', | |||||
| 'password' => 'test', | |||||
| ] | |||||
| ]) | |||||
| ->assertSuccessful() | |||||
| ->content(); | |||||
| $authResponseData = json_decode($response, true); | |||||
| $token = $authResponseData['token']; | |||||
| $token = $this->JWTManager->create($user->object()); | |||||
| $this->browser() | $this->browser() | ||||
| ->post('/api/posts' , [ | ->post('/api/posts' , [ | ||||
| @@ -8,6 +8,7 @@ | |||||
| namespace App\Tests\Functional; | namespace App\Tests\Functional; | ||||
| use App\Factory\UserFactory; | use App\Factory\UserFactory; | ||||
| use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface; | |||||
| use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; | use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; | ||||
| use Zenstruck\Browser\Json; | use Zenstruck\Browser\Json; | ||||
| use Zenstruck\Browser\Test\HasBrowser; | use Zenstruck\Browser\Test\HasBrowser; | ||||
| @@ -20,6 +21,14 @@ class UserResourceTest extends KernelTestCase | |||||
| use ResetDatabase; | use ResetDatabase; | ||||
| use Factories; | use Factories; | ||||
| private JWTTokenManagerInterface $JWTManager; | |||||
| protected function setUp(): void | |||||
| { | |||||
| parent::setUp(); | |||||
| $this->JWTManager = self::getContainer()->get('lexik_jwt_authentication.jwt_manager'); | |||||
| } | |||||
| public function testPostUser(): void | public function testPostUser(): void | ||||
| { | { | ||||
| UserFactory::createOne( | UserFactory::createOne( | ||||
| @@ -79,7 +88,7 @@ class UserResourceTest extends KernelTestCase | |||||
| public function testPostUserNoAdmin(): void | public function testPostUserNoAdmin(): void | ||||
| { | { | ||||
| UserFactory::createOne( | |||||
| $user = UserFactory::createOne( | |||||
| [ | [ | ||||
| 'email' => 'peter@test.de', | 'email' => 'peter@test.de', | ||||
| 'firstName' => 'Peter', | 'firstName' => 'Peter', | ||||
| @@ -87,18 +96,8 @@ class UserResourceTest extends KernelTestCase | |||||
| 'password' => 'test', | 'password' => 'test', | ||||
| ] | ] | ||||
| ); | ); | ||||
| $response = $this->browser() | |||||
| ->post('/auth', [ | |||||
| 'json' => [ | |||||
| 'email' => 'peter@test.de', | |||||
| 'password' => 'test', | |||||
| ] | |||||
| ]) | |||||
| ->assertSuccessful() | |||||
| ->content(); | |||||
| $authResponseData = json_decode($response, true); | |||||
| $token = $authResponseData['token']; | |||||
| $token = $this->JWTManager->create($user->object()); | |||||
| $this->browser() | $this->browser() | ||||
| ->post('/api/users' , [ | ->post('/api/users' , [ | ||||
| 'json' => [ | 'json' => [ | ||||