diff --git a/config/packages/nelmio_api_doc.yaml b/config/packages/nelmio_api_doc.yaml index dc18a0cd..14bc2b4c 100644 --- a/config/packages/nelmio_api_doc.yaml +++ b/config/packages/nelmio_api_doc.yaml @@ -5,6 +5,7 @@ nelmio_api_doc: - { alias: CustomerEntity, type: App\Entity\Customer, groups: [Default, Entity, Customer] } - { alias: ProjectEntity, type: App\Entity\Project, groups: [Default, Entity, Project] } - { alias: ActivityEntity, type: App\Entity\Activity, groups: [Default, Entity, Activity] } + - { alias: ActivityEditForm, type: App\Form\ActivityEditForm, groups: [Default, Entity, Activity] } - { alias: TimesheetEditForm, type: App\Form\TimesheetEditForm, groups: [Default, Entity, Timesheet] } - { alias: TimesheetEntity, type: App\Entity\Timesheet, groups: [Default, Entity, Timesheet] } - { alias: UserEntity, type: App\Entity\User, groups: [Default, Entity, User] } diff --git a/src/API/ActivityController.php b/src/API/ActivityController.php index 3e29d340..1471bee4 100644 --- a/src/API/ActivityController.php +++ b/src/API/ActivityController.php @@ -11,6 +11,8 @@ declare(strict_types=1); namespace App\API; +use App\Entity\Activity; +use App\Form\ActivityEditForm; use App\Repository\ActivityRepository; use App\Repository\Query\ActivityQuery; use FOS\RestBundle\Controller\Annotations as Rest; @@ -20,6 +22,7 @@ use FOS\RestBundle\View\View; use FOS\RestBundle\View\ViewHandlerInterface; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security; use Swagger\Annotations as SWG; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; /** @@ -127,4 +130,113 @@ class ActivityController extends BaseApiController return $this->viewHandler->handle($view); } + + /** + * @SWG\Post( + * description="Creates a new activity entry and returns it afterwards", + * @SWG\Response( + * response=200, + * description="Returns the new created activity entry", + * @SWG\Schema(ref="#/definitions/ActivityEntity"), + * ) + * ) + * @SWG\Parameter( + * name="body", + * in="body", + * required=true, + * @SWG\Schema(ref="#/definitions/ActivityEditForm") + * ) + * + * @param Request $request + * @return Response + * @throws \App\Repository\RepositoryException + * @throws \Doctrine\ORM\ORMException + * @throws \Doctrine\ORM\OptimisticLockException + */ + public function postAction(Request $request) + { + if (!$this->isGranted('create_activity')) { + throw $this->createAccessDeniedException('User cannot create activities'); + } + + $activity = new Activity(); + + $form = $this->createForm(ActivityEditForm::class, $activity, [ + 'csrf_protection' => false, + ]); + + $form->submit($request->request->all()); + + if ($form->isValid()) { + if (null !== $activity->getId()) { + return new Response('This method does not support updates', Response::HTTP_BAD_REQUEST); + } + + $entityManager = $this->getDoctrine()->getManager(); + $entityManager->persist($activity); + $entityManager->flush(); + + $view = new View($activity, 200); + $view->getContext()->setGroups(['Default', 'Entity', 'Activity']); + + return $this->viewHandler->handle($view); + } + + $view = new View($form); + $view->getContext()->setGroups(['Default', 'Entity', 'Activity']); + + return $this->viewHandler->handle($view); + } + + /** + * @SWG\Patch( + * description="Update an existing activity entry, you can pass all or just a subset of all attributes", + * @SWG\Response( + * response=200, + * description="Returns the updated activity entry", + * @SWG\Schema(ref="#/definitions/ActivityEntity") + * ) + * ) + * @SWG\Parameter( + * name="body", + * in="body", + * required=true, + * @SWG\Schema(ref="#/definitions/ActivityEditForm") + * ) + * + * @param Request $request + * @param string $id + * @return Response + */ + public function patchAction(Request $request, string $id) + { + $activity = $this->repository->find($id); + + if (!$this->isGranted('edit', $activity)) { + throw $this->createAccessDeniedException('User cannot update activity'); + } + + $form = $this->createForm(ActivityEditForm::class, $activity, [ + 'csrf_protection' => false, + ]); + + $form->setData($activity); + $form->submit($request->request->all(), false); + + if (false === $form->isValid()) { + $view = new View($form, Response::HTTP_OK); + $view->getContext()->setGroups(['Default', 'Entity', 'Activity']); + + return $this->viewHandler->handle($view); + } + + $entityManager = $this->getDoctrine()->getManager(); + $entityManager->persist($activity); + $entityManager->flush(); + + $view = new View($activity, Response::HTTP_OK); + $view->getContext()->setGroups(['Default', 'Entity', 'Activity']); + + return $this->viewHandler->handle($view); + } } diff --git a/src/Form/ActivityEditForm.php b/src/Form/ActivityEditForm.php index 5c0e98b8..23ef3557 100644 --- a/src/Form/ActivityEditForm.php +++ b/src/Form/ActivityEditForm.php @@ -35,17 +35,22 @@ class ActivityEditForm extends AbstractType */ public function buildForm(FormBuilderInterface $builder, array $options) { - /** @var Activity $entry */ - $entry = $options['data']; - $project = null; $customer = null; $currency = false; + $id = null; - if (null !== $entry->getProject()) { - $project = $entry->getProject(); - $customer = $project->getCustomer(); - $currency = $customer->getCurrency(); + if (isset($options['data'])) { + /** @var Activity $entry */ + $entry = $options['data']; + + if (null !== $entry->getProject()) { + $project = $entry->getProject(); + $customer = $project->getCustomer(); + $currency = $customer->getCurrency(); + } + + $id = $entry->getId(); } $builder @@ -114,7 +119,7 @@ class ActivityEditForm extends AbstractType ]) ; - if (null === $entry->getId()) { + if (null === $id) { $builder->add('create_more', CheckboxType::class, [ 'label' => 'label.create_more', 'required' => false, diff --git a/tests/API/ActivityControllerTest.php b/tests/API/ActivityControllerTest.php index f81d8970..272f0971 100644 --- a/tests/API/ActivityControllerTest.php +++ b/tests/API/ActivityControllerTest.php @@ -15,6 +15,7 @@ use App\Entity\Project; use App\Entity\User; use App\Repository\Query\VisibilityQuery; use Symfony\Bundle\FrameworkBundle\Client; +use Symfony\Component\HttpFoundation\Response; /** * @coversDefaultClass \App\API\ActivityController @@ -128,11 +129,100 @@ class ActivityControllerTest extends APIControllerBaseTest $this->assertEquals($expectedKeys, $actual); } + public function testPostAction() + { + $client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN); + $data = [ + 'name' => 'foo', + 'customer' => 1, + 'project' => 1, + 'visible' => true + ]; + $this->request($client, '/api/activities', 'POST', [], json_encode($data)); + $this->assertTrue($client->getResponse()->isSuccessful()); + + $result = json_decode($client->getResponse()->getContent(), true); + $this->assertIsArray($result); + $this->assertStructure($result); + $this->assertNotEmpty($result['id']); + } + + public function testPostActionWithInvalidUser() + { + $client = $this->getClientForAuthenticatedUser(User::ROLE_USER); + $data = [ + 'name' => 'foo', + 'customer' => 1, + 'project' => 1, + 'visible' => true + ]; + $this->request($client, '/api/activities', 'POST', [], json_encode($data)); + $response = $client->getResponse(); + $this->assertFalse($response->isSuccessful()); + $this->assertEquals(Response::HTTP_FORBIDDEN, $response->getStatusCode()); + $json = json_decode($response->getContent(), true); + $this->assertEquals('User cannot create activities', $json['message']); + } + public function testNotFound() { $this->assertEntityNotFound(User::ROLE_USER, '/api/activities/2'); } + public function testPatchAction() + { + $client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN); + $data = [ + 'name' => 'foo', + 'comment' => '', + 'customer' => 1, + 'project' => 1, + 'visible' => true + ]; + $this->request($client, '/api/activities/1', 'PATCH', [], json_encode($data)); + $this->assertTrue($client->getResponse()->isSuccessful()); + + $result = json_decode($client->getResponse()->getContent(), true); + $this->assertIsArray($result); + $this->assertStructure($result); + $this->assertNotEmpty($result['id']); + } + + public function testPatchActionWithInvalidUser() + { + $client = $this->getClientForAuthenticatedUser(User::ROLE_USER); + + $data = [ + 'name' => 'foo', + 'comment' => '', + 'customer' => 1, + 'project' => 1, + 'visible' => true + ]; + $this->request($client, '/api/activities/15', 'PATCH', [], json_encode($data)); + $response = $client->getResponse(); + $this->assertFalse($response->isSuccessful()); + $this->assertEquals(Response::HTTP_FORBIDDEN, $response->getStatusCode()); + $json = json_decode($response->getContent(), true); + $this->assertEquals('User cannot update activity', $json['message']); + } + + public function testInvalidPatchAction() + { + $client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN); + $data = [ + 'name' => 'foo', + 'customer' => 255, + 'project' => 1, + 'visible' => true + ]; + $this->request($client, '/api/activities/1', 'PATCH', [], json_encode($data)); + + $response = $client->getResponse(); + $this->assertEquals(400, $response->getStatusCode()); + $this->assertApiCallValidationError($response, ['project']); + } + protected function assertStructure(array $result, $full = true) { $expectedKeys = ['id', 'name', 'visible'];