diff --git a/src/ApiResource/ContactApi.php b/src/ApiResource/ContactApi.php index 59725bf..ca21d46 100644 --- a/src/ApiResource/ContactApi.php +++ b/src/ApiResource/ContactApi.php @@ -78,7 +78,9 @@ class ContactApi /** * @var array */ + #[ApiProperty(writable: false)] public array $postings = []; + #[ApiProperty(writable: false)] public ?\DateTimeImmutable $createdAt = null; } \ No newline at end of file diff --git a/src/ApiResource/PostingApi.php b/src/ApiResource/PostingApi.php index 0dd3265..f361b84 100644 --- a/src/ApiResource/PostingApi.php +++ b/src/ApiResource/PostingApi.php @@ -60,7 +60,8 @@ class PostingApi #[NotBlank] public ?string $message = null; - #[IsValidOwner] + //#[IsValidOwner] + #[ApiProperty(writable: false)] public ?UserApi $owner = null; #[ApiProperty(writable: false)] diff --git a/src/ApiResource/UserApi.php b/src/ApiResource/UserApi.php index d3656b6..ac072d0 100644 --- a/src/ApiResource/UserApi.php +++ b/src/ApiResource/UserApi.php @@ -29,7 +29,7 @@ use Symfony\Component\Validator\Constraints as Assert; security: 'is_granted("ROLE_USER")' ), new Post( - security: 'is_granted("PUBLIC_ACCESS")', + security: 'is_granted("ROLE_ADMIN")', validationContext: ['groups' => ['Default', 'postValidation']], ), new Patch( diff --git a/src/Entity/Contact.php b/src/Entity/Contact.php index 030f4d8..022f6dc 100644 --- a/src/Entity/Contact.php +++ b/src/Entity/Contact.php @@ -48,8 +48,9 @@ class Contact #[ORM\OneToMany(mappedBy: 'contact', targetEntity: Posting::class, orphanRemoval: true)] private Collection $postings; - public function __construct() + public function __construct(Partner $partner) { + $this->partner = $partner; $this->createdAt = new \DateTimeImmutable(); $this->postings = new ArrayCollection(); } diff --git a/src/Entity/Posting.php b/src/Entity/Posting.php index 7f8420e..81e9902 100644 --- a/src/Entity/Posting.php +++ b/src/Entity/Posting.php @@ -39,8 +39,11 @@ class Posting #[ORM\OneToMany(mappedBy: 'posting', targetEntity: Comment::class, orphanRemoval: true)] private Collection $comments; - public function __construct() + public function __construct(User $owner, Partner $partner, Contact $contact = null) { + $this->owner = $owner; + $this->partner = $partner; + $this->contact = $contact; $this->createdAt = new \DateTimeImmutable(); $this->comments = new ArrayCollection(); } @@ -72,13 +75,6 @@ class Posting return $this->owner; } - public function setOwner(?User $owner): static - { - $this->owner = $owner; - - return $this; - } - public function getHeadline(): ?string { return $this->headline; diff --git a/src/Mapper/ContactApiToEntityMapper.php b/src/Mapper/ContactApiToEntityMapper.php index 4369fc1..c3976fa 100644 --- a/src/Mapper/ContactApiToEntityMapper.php +++ b/src/Mapper/ContactApiToEntityMapper.php @@ -4,6 +4,8 @@ namespace App\Mapper; use App\ApiResource\ContactApi; use App\Entity\Contact; +use App\Entity\Partner; +use App\Repository\ContactRepository; use App\Repository\PartnerRepository; use Symfony\Bundle\SecurityBundle\Security; use Symfonycasts\MicroMapper\AsMapper; @@ -14,7 +16,7 @@ use Symfonycasts\MicroMapper\MicroMapperInterface; class ContactApiToEntityMapper implements MapperInterface { public function __construct( - private PartnerRepository $repository, + private ContactRepository $repository, private Security $security, private MicroMapperInterface $microMapper, ) @@ -27,9 +29,21 @@ class ContactApiToEntityMapper implements MapperInterface $dto = $from; assert($dto instanceof ContactApi); - $entity = $dto->id ? $this->repository->find($dto->id) : new Contact(); + if ($dto->id) { + $entity = $this->repository->find($dto->id); + } else { + if ($dto->partner === null) { + throw new \Exception('Partner missing'); + } + $partner = $this->microMapper->map($dto->partner, Partner::class, [ + MicroMapperInterface::MAX_DEPTH => 0, + ]); + $entity = new Contact($partner); + } + + if (!$entity) { - throw new \Exception('Partner not found'); + throw new \Exception('Contact not found'); } return $entity; diff --git a/src/Mapper/PostingApiToEntityMapper.php b/src/Mapper/PostingApiToEntityMapper.php index 52cc395..d783a76 100644 --- a/src/Mapper/PostingApiToEntityMapper.php +++ b/src/Mapper/PostingApiToEntityMapper.php @@ -2,12 +2,12 @@ namespace App\Mapper; -use App\ApiResource\DragonTreasureApi; +use App\ApiResource\ContactApi; use App\ApiResource\PostingApi; -use App\Entity\DragonTreasure; +use App\Entity\Contact; +use App\Entity\Partner; use App\Entity\User; use App\Entity\Posting; -use App\Repository\DragonTreasureRepository; use App\Repository\PostingRepository; use Symfony\Bundle\SecurityBundle\Security; use Symfonycasts\MicroMapper\AsMapper; @@ -31,7 +31,29 @@ class PostingApiToEntityMapper implements MapperInterface $dto = $from; assert($dto instanceof PostingApi); - $entity = $dto->id ? $this->repository->find($dto->id) : new Posting(); + if ($dto->id) { + $entity = $this->repository->find($dto->id); + } else { + $user = $this->security->getUser(); + assert($user instanceof User); + if ($dto->partner === null) { + throw new \Exception('Partner missing'); + } + $partner = $this->microMapper->map($dto->partner, Partner::class, [ + MicroMapperInterface::MAX_DEPTH => 0, + ]); + $contact = null; + if ($dto->contact) { + assert($dto->contact instanceof ContactApi); + $contact = $this->microMapper->map($dto->contact, Contact::class, [ + MicroMapperInterface::MAX_DEPTH => 0, + ]); + } + assert($partner instanceof Partner); + assert($contact === null || $contact instanceof Contact); + $entity = new Posting($user, $partner, $contact); + } + if (!$entity) { throw new \Exception('Posting not found'); } @@ -46,16 +68,7 @@ class PostingApiToEntityMapper implements MapperInterface $entity = $to; assert($entity instanceof Posting); - if ($dto->owner) { - $entity->setOwner($this->microMapper->map($dto->owner, User::class, [ - MicroMapperInterface::MAX_DEPTH => 0, - ])); - } else { - $entity->setOwner($this->security->getUser()); - } - - $entity->setPartner($dto->partner); - $entity->setContact($dto->contact); + $entity->setHeadline($dto->headline); $entity->setMessage($dto->message); return $entity; diff --git a/tests/Functional/PostingResourceTest.php b/tests/Functional/PostingResourceTest.php new file mode 100644 index 0000000..bc92ac0 --- /dev/null +++ b/tests/Functional/PostingResourceTest.php @@ -0,0 +1,81 @@ + + * @date 12.12.23 + */ + + +namespace App\Tests\Functional; + +use App\Factory\ContactFactory; +use App\Factory\MediaObjectLogoFactory; +use App\Factory\MediaObjectProfileFactory; +use App\Factory\PartnerFactory; +use App\Factory\PostingFactory; +use App\Factory\UserFactory; +use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; +use Zenstruck\Browser\Test\HasBrowser; +use Zenstruck\Foundry\Test\Factories; +use Zenstruck\Foundry\Test\ResetDatabase; + +class PostingResourceTest extends KernelTestCase +{ + use HasBrowser; + use ResetDatabase; + use Factories; + + public function testPostPosting(): void + { + UserFactory::createOne( + [ + 'email' => 'peter@test.de', + 'firstName' => 'Peter', + 'lastName' => 'Test', + 'password' => 'test', + ] + ); + + MediaObjectProfileFactory::createOne(); + MediaObjectLogoFactory::createOne(); + $partner = PartnerFactory::createOne(); + $contact = ContactFactory::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']; + + $this->browser() + ->post('/api/posts' , [ + 'json' => [ + 'headline' => 'hello', + 'message' => 'world', + 'partner' => '/api/partners/' . $partner->getId(), + 'contact' => '/api/contacts/' . $contact->getId(), + ], + 'headers' => [ + 'Authorization' => 'Bearer ' . $token, + ] + ]) + ->assertSuccessful() + ; + + $this->browser() + ->get('/api/posts', [ + 'headers' => [ + 'Authorization' => 'Bearer ' . $token, + ], + ]) + ->assertSuccessful() + ->assertJsonMatches('"hydra:totalItems"', 2) + ; + } +} \ No newline at end of file diff --git a/tests/Functional/UserResourceTest.php b/tests/Functional/UserResourceTest.php index d6ef843..8e20daa 100644 --- a/tests/Functional/UserResourceTest.php +++ b/tests/Functional/UserResourceTest.php @@ -20,95 +20,98 @@ class UserResourceTest extends KernelTestCase use ResetDatabase; use Factories; - public function testPostToCreateUser(): void + public function testPostUser(): void { - $this->browser() - ->post('/api/users', [ + UserFactory::createOne( + [ + 'email' => 'peter@test.de', + 'firstName' => 'Peter', + 'lastName' => 'Test', + 'password' => 'test', + 'roles' => ["ROLE_ADMIN"] + ] + ); + $response = $this->browser() + ->post('/auth', [ 'json' => [ - 'email' => 'draggin_in_the_morning@coffee.com', - 'firstName' => 'Danny', - 'lastName' => 'Boy', - 'password' => 'password', + 'email' => 'peter@test.de', + 'password' => 'test', ] ]) - ->assertStatus(201) ->use(function (Json $json) { - $json->assertMissing('password'); - $json->assertMissing('id'); + $json->assertHas('token'); + $json->assertHas('id'); + $json->assertHas('email'); + $json->assertHas('firstName'); + $json->assertHas('lastName'); + $json->assertHas('roles'); }) - ->post('/auth', [ + ->assertSuccessful() + ->content(); + $authResponseData = json_decode($response, true); + $token = $authResponseData['token']; + + $this->browser() + ->post('/api/users' , [ 'json' => [ - 'email' => 'draggin_in_the_morning@coffee.com', - 'password' => 'password', + 'email' => 'norbert@test.de', + 'firstName' => 'Norbert', + 'lastName' => 'Test', + 'password' => 'sehr geheim', + ], + 'headers' => [ + 'Authorization' => 'Bearer ' . $token, ] ]) ->assertSuccessful() ; - } - - public function testGetUsersWithoutAuthentication() - { - UserFactory::createMany(2); - $this->browser() - ->get('/api/users') - ->assertStatus(401) - ; - } - - public function testGetOneUserWithoutAuthentication() - { - UserFactory::createOne(); - $this->browser() - ->get('/api/users/1') - ->assertStatus(401) - ; - } - - public function testPatchUserAsSameUser() - { - $user = UserFactory::createOne( - [ - 'firstName' => 'John', - 'lastName' => 'Doe' - ] - ); $this->browser() - ->actingAs($user) - ->patch('/api/users/' . $user->getId(), [ + ->post('/auth', [ 'json' => [ - 'firstName' => 'Joe', - 'lastName' => 'Black' - ], - 'headers' => ['Content-Type' => 'application/merge-patch+json'] + 'email' => 'norbert@test.de', + 'password' => 'sehr geheim', + ] ]) - ->assertStatus(200) - ->get('/api/users/' . $user->getId()) - ->assertStatus(200) - ->assertJsonMatches('firstName', 'Joe') - ->assertJsonMatches('lastName', 'Black') - ; + ->assertSuccessful() + ->assertJsonMatches('email', 'norbert@test.de'); } - public function testPatchUserInactiveAsSameUser() + public function testPostUserNoAdmin(): void { - $user = UserFactory::createOne( + UserFactory::createOne( [ - 'firstName' => 'John' + 'email' => 'peter@test.de', + 'firstName' => 'Peter', + 'lastName' => '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']; $this->browser() - ->actingAs($user) - ->patch('/api/users/' . $user->getId(), [ + ->post('/api/users' , [ 'json' => [ - 'firstName' => 'Joe' + 'email' => 'norbert@test.de', + 'firstName' => 'Norbert', + 'lastName' => 'Test', + 'password' => 'sehr geheim', ], - 'headers' => ['Content-Type' => 'application/merge-patch+json'] + 'headers' => [ + 'Authorization' => 'Bearer ' . $token, + ] ]) - ->assertStatus(200) - ->get('/api/users/' . $user->getId()) - ->assertJsonMatches('firstName', 'A shiny thing') + ->assertStatus(403); ; } } \ No newline at end of file