| @@ -78,7 +78,9 @@ class ContactApi | |||
| /** | |||
| * @var array<int, PostingApi> | |||
| */ | |||
| #[ApiProperty(writable: false)] | |||
| public array $postings = []; | |||
| #[ApiProperty(writable: false)] | |||
| public ?\DateTimeImmutable $createdAt = null; | |||
| } | |||
| @@ -60,7 +60,8 @@ class PostingApi | |||
| #[NotBlank] | |||
| public ?string $message = null; | |||
| #[IsValidOwner] | |||
| //#[IsValidOwner] | |||
| #[ApiProperty(writable: false)] | |||
| public ?UserApi $owner = null; | |||
| #[ApiProperty(writable: false)] | |||
| @@ -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( | |||
| @@ -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(); | |||
| } | |||
| @@ -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; | |||
| @@ -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; | |||
| @@ -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; | |||
| @@ -0,0 +1,81 @@ | |||
| <?php | |||
| /** | |||
| * @author Daniel Knudsen <d.knudsen@spawntree.de> | |||
| * @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) | |||
| ; | |||
| } | |||
| } | |||
| @@ -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); | |||
| ; | |||
| } | |||
| } | |||