diff --git a/src/API/ActivityController.php b/src/API/ActivityController.php index d661cd05..f43c6fb1 100644 --- a/src/API/ActivityController.php +++ b/src/API/ActivityController.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace App\API; use App\Entity\Activity; +use App\Event\ActivityMetaDefinitionEvent; use App\Form\API\ActivityApiEditForm; use App\Repository\ActivityRepository; use App\Repository\Query\ActivityQuery; @@ -22,6 +23,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\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; @@ -36,16 +38,21 @@ class ActivityController extends BaseApiController /** * @var ActivityRepository */ - protected $repository; + private $repository; /** * @var ViewHandlerInterface */ - protected $viewHandler; + private $viewHandler; + /** + * @var EventDispatcherInterface + */ + private $dispatcher; - public function __construct(ViewHandlerInterface $viewHandler, ActivityRepository $repository) + public function __construct(ViewHandlerInterface $viewHandler, ActivityRepository $repository, EventDispatcherInterface $dispatcher) { $this->viewHandler = $viewHandler; $this->repository = $repository; + $this->dispatcher = $dispatcher; } /** @@ -169,14 +176,15 @@ class ActivityController extends BaseApiController $activity = new Activity(); + $event = new ActivityMetaDefinitionEvent($activity); + $this->dispatcher->dispatch($event, ActivityMetaDefinitionEvent::class); + $form = $this->createForm(ActivityApiEditForm::class, $activity); $form->submit($request->request->all()); if ($form->isValid()) { - $entityManager = $this->getDoctrine()->getManager(); - $entityManager->persist($activity); - $entityManager->flush(); + $this->repository->saveActivity($activity); $view = new View($activity, 200); $view->getContext()->setGroups(['Default', 'Entity', 'Activity']); @@ -218,6 +226,8 @@ class ActivityController extends BaseApiController * @param Request $request * @param string $id * @return Response + * @throws \Doctrine\ORM\ORMException + * @throws \Doctrine\ORM\OptimisticLockException */ public function patchAction(Request $request, string $id) { @@ -231,6 +241,9 @@ class ActivityController extends BaseApiController throw new AccessDeniedHttpException('User cannot update activity'); } + $event = new ActivityMetaDefinitionEvent($activity); + $this->dispatcher->dispatch($event, ActivityMetaDefinitionEvent::class); + $form = $this->createForm(ActivityApiEditForm::class, $activity); $form->setData($activity); @@ -243,13 +256,67 @@ class ActivityController extends BaseApiController return $this->viewHandler->handle($view); } - $entityManager = $this->getDoctrine()->getManager(); - $entityManager->persist($activity); - $entityManager->flush(); + $this->repository->saveActivity($activity); $view = new View($activity, Response::HTTP_OK); $view->getContext()->setGroups(['Default', 'Entity', 'Activity']); return $this->viewHandler->handle($view); } + + /** + * Sets the value of a meta-field for an existing activity. + * + * @SWG\Response( + * response=200, + * description="Sets the value of an existing/configured meta-field. You cannot create unknown meta-fields, if the given name is not a configured meta-field, this will return an exception.", + * @SWG\Schema(ref="#/definitions/ActivityEntity") + * ) + * @SWG\Parameter( + * name="id", + * in="path", + * type="integer", + * description="Activity record ID to set the meta-field value for", + * required=true, + * ) + * @Rest\RequestParam(name="name", strict=true, nullable=false, description="The meta-field name") + * @Rest\RequestParam(name="value", strict=true, nullable=false, description="The meta-field value") + * + * @param int $id + * @param ParamFetcherInterface $paramFetcher + * @return Response + * @throws \Doctrine\ORM\ORMException + * @throws \Doctrine\ORM\OptimisticLockException + */ + public function metaAction($id, ParamFetcherInterface $paramFetcher) + { + $activity = $this->repository->find($id); + + if (null === $activity) { + throw new NotFoundException(); + } + + if (!$this->isGranted('edit', $activity)) { + throw new AccessDeniedHttpException('You are not allowed to update this activity'); + } + + $event = new ActivityMetaDefinitionEvent($activity); + $this->dispatcher->dispatch($event, ActivityMetaDefinitionEvent::class); + + $name = $paramFetcher->get('name'); + $value = $paramFetcher->get('value'); + + if (null === ($meta = $activity->getMetaField($name))) { + throw new \InvalidArgumentException('Unknown meta-field requested'); + } + + $meta->setValue($value); + + $this->repository->saveActivity($activity); + + $view = new View($activity, 200); + $view->getContext()->setGroups(['Default', 'Entity', 'Project']); + + return $this->viewHandler->handle($view); + } } diff --git a/src/API/CustomerController.php b/src/API/CustomerController.php index efc1d827..823bad2a 100644 --- a/src/API/CustomerController.php +++ b/src/API/CustomerController.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace App\API; use App\Entity\Customer; +use App\Event\CustomerMetaDefinitionEvent; use App\Form\API\CustomerApiEditForm; use App\Repository\CustomerRepository; use App\Repository\Query\CustomerQuery; @@ -22,6 +23,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\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; @@ -36,16 +38,21 @@ class CustomerController extends BaseApiController /** * @var CustomerRepository */ - protected $repository; + private $repository; /** * @var ViewHandlerInterface */ - protected $viewHandler; + private $viewHandler; + /** + * @var EventDispatcherInterface + */ + private $dispatcher; - public function __construct(ViewHandlerInterface $viewHandler, CustomerRepository $repository) + public function __construct(ViewHandlerInterface $viewHandler, CustomerRepository $repository, EventDispatcherInterface $dispatcher) { $this->viewHandler = $viewHandler; $this->repository = $repository; + $this->dispatcher = $dispatcher; } /** @@ -148,14 +155,15 @@ class CustomerController extends BaseApiController $customer = new Customer(); + $event = new CustomerMetaDefinitionEvent($customer); + $this->dispatcher->dispatch($event, CustomerMetaDefinitionEvent::class); + $form = $this->createForm(CustomerApiEditForm::class, $customer); $form->submit($request->request->all()); if ($form->isValid()) { - $entityManager = $this->getDoctrine()->getManager(); - $entityManager->persist($customer); - $entityManager->flush(); + $this->repository->saveCustomer($customer); $view = new View($customer, 200); $view->getContext()->setGroups(['Default', 'Entity', 'Customer']); @@ -197,6 +205,8 @@ class CustomerController extends BaseApiController * @param Request $request * @param string $id * @return Response + * @throws \Doctrine\ORM\ORMException + * @throws \Doctrine\ORM\OptimisticLockException */ public function patchAction(Request $request, string $id) { @@ -210,6 +220,9 @@ class CustomerController extends BaseApiController throw new AccessDeniedHttpException('User cannot update customer'); } + $event = new CustomerMetaDefinitionEvent($customer); + $this->dispatcher->dispatch($event, CustomerMetaDefinitionEvent::class); + $form = $this->createForm(CustomerApiEditForm::class, $customer); $form->setData($customer); @@ -222,13 +235,67 @@ class CustomerController extends BaseApiController return $this->viewHandler->handle($view); } - $entityManager = $this->getDoctrine()->getManager(); - $entityManager->persist($customer); - $entityManager->flush(); + $this->repository->saveCustomer($customer); $view = new View($customer, Response::HTTP_OK); $view->getContext()->setGroups(['Default', 'Entity', 'Customer']); return $this->viewHandler->handle($view); } + + /** + * Sets the value of a meta-field for an existing customer. + * + * @SWG\Response( + * response=200, + * description="Sets the value of an existing/configured meta-field. You cannot create unknown meta-fields, if the given name is not a configured meta-field, this will return an exception.", + * @SWG\Schema(ref="#/definitions/CustomerEntity") + * ) + * @SWG\Parameter( + * name="id", + * in="path", + * type="integer", + * description="Customer record ID to set the meta-field value for", + * required=true, + * ) + * @Rest\RequestParam(name="name", strict=true, nullable=false, description="The meta-field name") + * @Rest\RequestParam(name="value", strict=true, nullable=false, description="The meta-field value") + * + * @param int $id + * @param ParamFetcherInterface $paramFetcher + * @return Response + * @throws \Doctrine\ORM\ORMException + * @throws \Doctrine\ORM\OptimisticLockException + */ + public function metaAction($id, ParamFetcherInterface $paramFetcher) + { + $customer = $this->repository->find($id); + + if (null === $customer) { + throw new NotFoundException(); + } + + if (!$this->isGranted('edit', $customer)) { + throw new AccessDeniedHttpException('You are not allowed to update this customer'); + } + + $event = new CustomerMetaDefinitionEvent($customer); + $this->dispatcher->dispatch($event, CustomerMetaDefinitionEvent::class); + + $name = $paramFetcher->get('name'); + $value = $paramFetcher->get('value'); + + if (null === ($meta = $customer->getMetaField($name))) { + throw new \InvalidArgumentException('Unknown meta-field requested'); + } + + $meta->setValue($value); + + $this->repository->saveCustomer($customer); + + $view = new View($customer, 200); + $view->getContext()->setGroups(['Default', 'Entity', 'Customer']); + + return $this->viewHandler->handle($view); + } } diff --git a/src/API/ProjectController.php b/src/API/ProjectController.php index 9d5b2dbe..4b365e51 100644 --- a/src/API/ProjectController.php +++ b/src/API/ProjectController.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace App\API; use App\Entity\Project; +use App\Event\ProjectMetaDefinitionEvent; use App\Form\API\ProjectApiEditForm; use App\Repository\ProjectRepository; use App\Repository\Query\ProjectQuery; @@ -22,6 +23,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\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; @@ -36,16 +38,21 @@ class ProjectController extends BaseApiController /** * @var ProjectRepository */ - protected $repository; + private $repository; /** * @var ViewHandlerInterface */ - protected $viewHandler; + private $viewHandler; + /** + * @var EventDispatcherInterface + */ + private $dispatcher; - public function __construct(ViewHandlerInterface $viewHandler, ProjectRepository $repository) + public function __construct(ViewHandlerInterface $viewHandler, ProjectRepository $repository, EventDispatcherInterface $dispatcher) { $this->viewHandler = $viewHandler; $this->repository = $repository; + $this->dispatcher = $dispatcher; } /** @@ -154,14 +161,15 @@ class ProjectController extends BaseApiController $project = new Project(); + $event = new ProjectMetaDefinitionEvent($project); + $this->dispatcher->dispatch($event, ProjectMetaDefinitionEvent::class); + $form = $this->createForm(ProjectApiEditForm::class, $project); $form->submit($request->request->all()); if ($form->isValid()) { - $entityManager = $this->getDoctrine()->getManager(); - $entityManager->persist($project); - $entityManager->flush(); + $this->repository->saveProject($project); $view = new View($project, 200); $view->getContext()->setGroups(['Default', 'Entity', 'Project']); @@ -203,6 +211,8 @@ class ProjectController extends BaseApiController * @param Request $request * @param string $id * @return Response + * @throws \Doctrine\ORM\ORMException + * @throws \Doctrine\ORM\OptimisticLockException */ public function patchAction(Request $request, string $id) { @@ -216,6 +226,9 @@ class ProjectController extends BaseApiController throw new AccessDeniedHttpException('User cannot update project'); } + $event = new ProjectMetaDefinitionEvent($project); + $this->dispatcher->dispatch($event, ProjectMetaDefinitionEvent::class); + $form = $this->createForm(ProjectApiEditForm::class, $project); $form->setData($project); @@ -228,13 +241,67 @@ class ProjectController extends BaseApiController return $this->viewHandler->handle($view); } - $entityManager = $this->getDoctrine()->getManager(); - $entityManager->persist($project); - $entityManager->flush(); + $this->repository->saveProject($project); $view = new View($project, Response::HTTP_OK); $view->getContext()->setGroups(['Default', 'Entity', 'Project']); return $this->viewHandler->handle($view); } + + /** + * Sets the value of a meta-field for an existing project. + * + * @SWG\Response( + * response=200, + * description="Sets the value of an existing/configured meta-field. You cannot create unknown meta-fields, if the given name is not a configured meta-field, this will return an exception.", + * @SWG\Schema(ref="#/definitions/ProjectEntity") + * ) + * @SWG\Parameter( + * name="id", + * in="path", + * type="integer", + * description="Project record ID to set the meta-field value for", + * required=true, + * ) + * @Rest\RequestParam(name="name", strict=true, nullable=false, description="The meta-field name") + * @Rest\RequestParam(name="value", strict=true, nullable=false, description="The meta-field value") + * + * @param int $id + * @param ParamFetcherInterface $paramFetcher + * @return Response + * @throws \Doctrine\ORM\ORMException + * @throws \Doctrine\ORM\OptimisticLockException + */ + public function metaAction($id, ParamFetcherInterface $paramFetcher) + { + $project = $this->repository->find($id); + + if (null === $project) { + throw new NotFoundException(); + } + + if (!$this->isGranted('edit', $project)) { + throw new AccessDeniedHttpException('You are not allowed to update this project'); + } + + $event = new ProjectMetaDefinitionEvent($project); + $this->dispatcher->dispatch($event, ProjectMetaDefinitionEvent::class); + + $name = $paramFetcher->get('name'); + $value = $paramFetcher->get('value'); + + if (null === ($meta = $project->getMetaField($name))) { + throw new \InvalidArgumentException('Unknown meta-field requested'); + } + + $meta->setValue($value); + + $this->repository->saveProject($project); + + $view = new View($project, 200); + $view->getContext()->setGroups(['Default', 'Entity', 'Project']); + + return $this->viewHandler->handle($view); + } } diff --git a/src/API/TimesheetController.php b/src/API/TimesheetController.php index 0cb5cd95..f68f9104 100644 --- a/src/API/TimesheetController.php +++ b/src/API/TimesheetController.php @@ -14,6 +14,7 @@ namespace App\API; use App\Configuration\TimesheetConfiguration; use App\Entity\Timesheet; use App\Entity\User; +use App\Event\TimesheetMetaDefinitionEvent; use App\Form\API\TimesheetApiEditForm; use App\Repository\Query\TimesheetQuery; use App\Repository\TagRepository; @@ -30,6 +31,7 @@ use FOS\RestBundle\View\ViewHandlerInterface; use Pagerfanta\Pagerfanta; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security; use Swagger\Annotations as SWG; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; @@ -47,27 +49,31 @@ class TimesheetController extends BaseApiController /** * @var TimesheetRepository */ - protected $repository; + private $repository; /** * @var ViewHandlerInterface */ - protected $viewHandler; + private $viewHandler; /** * @var TimesheetConfiguration */ - protected $configuration; + private $configuration; /** * @var UserDateTimeFactory */ - protected $dateTime; + private $dateTime; /** * @var TagRepository */ - protected $tagRepository; + private $tagRepository; /** * @var TrackingModeService */ - protected $trackingModeService; + private $trackingModeService; + /** + * @var EventDispatcherInterface + */ + private $dispatcher; public function __construct( ViewHandlerInterface $viewHandler, @@ -75,7 +81,8 @@ class TimesheetController extends BaseApiController UserDateTimeFactory $dateTime, TimesheetConfiguration $configuration, TagRepository $tagRepository, - TrackingModeService $trackingModeService + TrackingModeService $trackingModeService, + EventDispatcherInterface $dispatcher ) { $this->viewHandler = $viewHandler; $this->repository = $repository; @@ -83,6 +90,7 @@ class TimesheetController extends BaseApiController $this->dateTime = $dateTime; $this->tagRepository = $tagRepository; $this->trackingModeService = $trackingModeService; + $this->dispatcher = $dispatcher; } protected function getTrackingMode(): TrackingModeInterface @@ -282,6 +290,9 @@ class TimesheetController extends BaseApiController $timesheet->setUser($this->getUser()); $timesheet->setBegin($this->dateTime->createDateTime()); + $event = new TimesheetMetaDefinitionEvent($timesheet); + $this->dispatcher->dispatch($event, TimesheetMetaDefinitionEvent::class); + $mode = $this->getTrackingMode(); $form = $this->createForm(TimesheetApiEditForm::class, $timesheet, [ @@ -305,9 +316,7 @@ class TimesheetController extends BaseApiController ); } - $entityManager = $this->getDoctrine()->getManager(); - $entityManager->persist($timesheet); - $entityManager->flush(); + $this->repository->save($timesheet); $view = new View($timesheet, 200); $view->getContext()->setGroups(['Default', 'Entity', 'Timesheet']); @@ -362,6 +371,9 @@ class TimesheetController extends BaseApiController throw new AccessDeniedHttpException('You are not allowed to update this timesheet'); } + $event = new TimesheetMetaDefinitionEvent($timesheet); + $this->dispatcher->dispatch($event, TimesheetMetaDefinitionEvent::class); + $mode = $this->getTrackingMode(); $form = $this->createForm(TimesheetApiEditForm::class, $timesheet, [ @@ -382,9 +394,7 @@ class TimesheetController extends BaseApiController return $this->viewHandler->handle($view); } - $entityManager = $this->getDoctrine()->getManager(); - $entityManager->persist($timesheet); - $entityManager->flush(); + $this->repository->save($timesheet); $view = new View($timesheet, Response::HTTP_OK); $view->getContext()->setGroups(['Default', 'Entity', 'Timesheet']); @@ -588,8 +598,8 @@ class TimesheetController extends BaseApiController /** @var User $user */ $user = $this->getUser(); - $entry = new Timesheet(); - $entry + $copyTimesheet = new Timesheet(); + $copyTimesheet ->setBegin($this->dateTime->createDateTime()) ->setUser($user) ->setActivity($timesheet->getActivity()) @@ -598,29 +608,29 @@ class TimesheetController extends BaseApiController if (null !== ($copy = $paramFetcher->get('copy'))) { if (in_array($copy, ['rates', 'all'])) { - $entry->setHourlyRate($timesheet->getHourlyRate()); - $entry->setFixedRate($timesheet->getFixedRate()); + $copyTimesheet->setHourlyRate($timesheet->getHourlyRate()); + $copyTimesheet->setFixedRate($timesheet->getFixedRate()); } if (in_array($copy, ['description', 'all'])) { - $entry->setDescription($timesheet->getDescription()); + $copyTimesheet->setDescription($timesheet->getDescription()); } if (in_array($copy, ['tags', 'all'])) { foreach ($timesheet->getTags() as $tag) { - $entry->addTag($tag); + $copyTimesheet->addTag($tag); } } if (in_array($copy, ['meta', 'all'])) { foreach ($timesheet->getMetaFields() as $metaField) { $metaNew = clone $metaField; - $entry->setMetaField($metaNew); + $copyTimesheet->setMetaField($metaNew); } } } - $errors = $validator->validate($entry); + $errors = $validator->validate($copyTimesheet); if (count($errors) > 0) { throw new BadRequestHttpException($errors[0]->getPropertyPath() . ' = ' . $errors[0]->getMessage()); @@ -631,11 +641,9 @@ class TimesheetController extends BaseApiController $this->configuration->getActiveEntriesHardLimit() ); - $entityManager = $this->getDoctrine()->getManager(); - $entityManager->persist($entry); - $entityManager->flush(); + $this->repository->save($copyTimesheet); - $view = new View($entry, 200); + $view = new View($copyTimesheet, 200); $view->getContext()->setGroups(['Default', 'Entity', 'Timesheet']); return $this->viewHandler->handle($view); @@ -659,6 +667,8 @@ class TimesheetController extends BaseApiController * * @param int $id * @return Response + * @throws \Doctrine\ORM\ORMException + * @throws \Doctrine\ORM\OptimisticLockException */ public function exportAction($id) { @@ -677,9 +687,63 @@ class TimesheetController extends BaseApiController $timesheet->setExported(!$timesheet->isExported()); - $entityManager = $this->getDoctrine()->getManager(); - $entityManager->persist($timesheet); - $entityManager->flush(); + $this->repository->save($timesheet); + + $view = new View($timesheet, 200); + $view->getContext()->setGroups(['Default', 'Entity', 'Timesheet']); + + return $this->viewHandler->handle($view); + } + + /** + * Sets the value of a meta-field for an existing timesheet. + * + * @SWG\Response( + * response=200, + * description="Sets the value of an existing/configured meta-field. You cannot create unknown meta-fields, if the given name is not a configured meta-field, this will return an exception.", + * @SWG\Schema(ref="#/definitions/TimesheetEntity") + * ) + * @SWG\Parameter( + * name="id", + * in="path", + * type="integer", + * description="Timesheet record ID to set the meta-field value for", + * required=true, + * ) + * @Rest\RequestParam(name="name", strict=true, nullable=false, description="The meta-field name") + * @Rest\RequestParam(name="value", strict=true, nullable=false, description="The meta-field value") + * + * @param int $id + * @param ParamFetcherInterface $paramFetcher + * @return Response + * @throws \Doctrine\ORM\ORMException + * @throws \Doctrine\ORM\OptimisticLockException + */ + public function metaAction($id, ParamFetcherInterface $paramFetcher) + { + $timesheet = $this->repository->find($id); + + if (null === $timesheet) { + throw new NotFoundException(); + } + + if (!$this->isGranted('edit', $timesheet)) { + throw new AccessDeniedHttpException('You are not allowed to update this timesheet'); + } + + $event = new TimesheetMetaDefinitionEvent($timesheet); + $this->dispatcher->dispatch($event, TimesheetMetaDefinitionEvent::class); + + $name = $paramFetcher->get('name'); + $value = $paramFetcher->get('value'); + + if (null === ($meta = $timesheet->getMetaField($name))) { + throw new \InvalidArgumentException('Unknown meta-field requested'); + } + + $meta->setValue($value); + + $this->repository->save($timesheet); $view = new View($timesheet, 200); $view->getContext()->setGroups(['Default', 'Entity', 'Timesheet']); diff --git a/src/Repository/ActivityRepository.php b/src/Repository/ActivityRepository.php index 8ecf66c2..e6216657 100644 --- a/src/Repository/ActivityRepository.php +++ b/src/Repository/ActivityRepository.php @@ -26,6 +26,26 @@ use Pagerfanta\Pagerfanta; class ActivityRepository extends EntityRepository { + /** + * @param mixed $id + * @param null $lockMode + * @param null $lockVersion + * @return Activity|null + */ + public function find($id, $lockMode = null, $lockVersion = null) + { + /** @var Activity|null $activity */ + $activity = parent::find($id, $lockMode, $lockVersion); + if (null === $activity) { + return null; + } + + $loader = new ActivityLoader($this->getEntityManager()); + $loader->loadResults([$activity]); + + return $activity; + } + /** * @param Activity $activity * @throws ORMException diff --git a/src/Repository/CustomerRepository.php b/src/Repository/CustomerRepository.php index 105eec90..911004e2 100644 --- a/src/Repository/CustomerRepository.php +++ b/src/Repository/CustomerRepository.php @@ -28,6 +28,26 @@ use Pagerfanta\Pagerfanta; class CustomerRepository extends EntityRepository { + /** + * @param mixed $id + * @param null $lockMode + * @param null $lockVersion + * @return Customer|null + */ + public function find($id, $lockMode = null, $lockVersion = null) + { + /** @var Customer|null $customer */ + $customer = parent::find($id, $lockMode, $lockVersion); + if (null === $customer) { + return null; + } + + $loader = new CustomerLoader($this->getEntityManager()); + $loader->loadResults([$customer]); + + return $customer; + } + /** * @param Customer $customer * @throws ORMException diff --git a/src/Repository/ProjectRepository.php b/src/Repository/ProjectRepository.php index 27f43976..37a5a43d 100644 --- a/src/Repository/ProjectRepository.php +++ b/src/Repository/ProjectRepository.php @@ -25,11 +25,28 @@ use Doctrine\ORM\Query; use Doctrine\ORM\QueryBuilder; use Pagerfanta\Pagerfanta; -/** - * Class ProjectRepository - */ class ProjectRepository extends EntityRepository { + /** + * @param mixed $id + * @param null $lockMode + * @param null $lockVersion + * @return Project|null + */ + public function find($id, $lockMode = null, $lockVersion = null) + { + /** @var Project|null $project */ + $project = parent::find($id, $lockMode, $lockVersion); + if (null === $project) { + return null; + } + + $loader = new ProjectLoader($this->getEntityManager()); + $loader->loadResults([$project]); + + return $project; + } + /** * @param Project $project * @throws ORMException diff --git a/src/Repository/TeamRepository.php b/src/Repository/TeamRepository.php index 099a1427..6cb07589 100644 --- a/src/Repository/TeamRepository.php +++ b/src/Repository/TeamRepository.php @@ -22,6 +22,20 @@ use Pagerfanta\Pagerfanta; class TeamRepository extends EntityRepository { + public function find($id, $lockMode = null, $lockVersion = null) + { + /** @var Team|null $team */ + $team = parent::find($id, $lockMode, $lockVersion); + if (null === $team) { + return null; + } + + $loader = new TeamLoader($this->getEntityManager()); + $loader->loadResults([$team]); + + return $team; + } + /** * @param Team $team * @throws ORMException diff --git a/src/Repository/TimesheetRepository.php b/src/Repository/TimesheetRepository.php index fdab2fae..65d6981a 100644 --- a/src/Repository/TimesheetRepository.php +++ b/src/Repository/TimesheetRepository.php @@ -35,6 +35,26 @@ class TimesheetRepository extends EntityRepository public const STATS_QUERY_ACTIVE = 'active'; public const STATS_QUERY_MONTHLY = 'monthly'; + /** + * @param mixed $id + * @param null $lockMode + * @param null $lockVersion + * @return Timesheet|null + */ + public function find($id, $lockMode = null, $lockVersion = null) + { + /** @var Timesheet|null $timesheet */ + $timesheet = parent::find($id, $lockMode, $lockVersion); + if (null === $timesheet) { + return null; + } + + $loader = new TimesheetLoader($this->getEntityManager()); + $loader->loadResults([$timesheet]); + + return $timesheet; + } + /** * @param Timesheet $timesheet * @throws \Doctrine\ORM\ORMException diff --git a/tests/API/APIControllerBaseTest.php b/tests/API/APIControllerBaseTest.php index 01e79680..17cb4261 100644 --- a/tests/API/APIControllerBaseTest.php +++ b/tests/API/APIControllerBaseTest.php @@ -151,6 +151,14 @@ abstract class APIControllerBaseTest extends ControllerBaseTest } protected function assertEntityNotFoundForPatch(string $role, string $url, array $data) + { + return $this->assertExceptionForPatchAction($role, $url, $data, [ + 'code' => 404, + 'message' => 'Not found' + ]); + } + + protected function assertExceptionForPatchAction(string $role, string $url, array $data, array $expectedErrors) { $client = $this->getClientForAuthenticatedUser($role); @@ -158,15 +166,10 @@ abstract class APIControllerBaseTest extends ControllerBaseTest $response = $client->getResponse(); $this->assertFalse($response->isSuccessful()); - $expected = [ - 'code' => 404, - 'message' => 'Not found' - ]; - - $this->assertEquals(404, $client->getResponse()->getStatusCode()); + $this->assertEquals($expectedErrors['code'], $client->getResponse()->getStatusCode()); $this->assertEquals( - $expected, + $expectedErrors, json_decode($client->getResponse()->getContent(), true) ); } diff --git a/tests/API/ActivityControllerTest.php b/tests/API/ActivityControllerTest.php index 34a5f6e2..6170052b 100644 --- a/tests/API/ActivityControllerTest.php +++ b/tests/API/ActivityControllerTest.php @@ -13,6 +13,7 @@ use App\Entity\Activity; use App\Entity\Customer; use App\Entity\Project; use App\Entity\User; +use App\Tests\Mocks\ActivityTestMetaFieldSubscriberMock; use Symfony\Bundle\FrameworkBundle\Client; use Symfony\Component\HttpFoundation\Response; @@ -219,6 +220,54 @@ class ActivityControllerTest extends APIControllerBaseTest $this->assertApiCallValidationError($response, ['project']); } + public function testMetaActionThrowsNotFound() + { + $this->assertEntityNotFoundForPatch(User::ROLE_ADMIN, '/api/activities/42/meta', []); + } + + public function testMetaActionThrowsExceptionOnMissingName() + { + return $this->assertExceptionForPatchAction(User::ROLE_ADMIN, '/api/activities/1/meta', ['value' => 'X'], [ + 'code' => 400, + 'message' => 'Parameter "name" of value "NULL" violated a constraint "This value should not be null."' + ]); + } + + public function testMetaActionThrowsExceptionOnMissingValue() + { + return $this->assertExceptionForPatchAction(User::ROLE_ADMIN, '/api/activities/1/meta', ['name' => 'X'], [ + 'code' => 400, + 'message' => 'Parameter "value" of value "NULL" violated a constraint "This value should not be null."' + ]); + } + + public function testMetaActionThrowsExceptionOnMissingMetafield() + { + return $this->assertExceptionForPatchAction(User::ROLE_ADMIN, '/api/activities/1/meta', ['name' => 'X', 'value' => 'Y'], [ + 'code' => 500, + 'message' => 'Unknown meta-field requested' + ]); + } + + public function testMetaAction() + { + $client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN); + $client->getContainer()->get('event_dispatcher')->addSubscriber(new ActivityTestMetaFieldSubscriberMock()); + + $data = [ + 'name' => 'metatestmock', + 'value' => 'another,testing,bar' + ]; + $this->request($client, '/api/activities/1/meta', 'PATCH', [], json_encode($data)); + + $this->assertTrue($client->getResponse()->isSuccessful()); + + $em = $client->getContainer()->get('doctrine.orm.entity_manager'); + /** @var Activity $activity */ + $activity = $em->getRepository(Activity::class)->find(1); + $this->assertEquals('another,testing,bar', $activity->getMetaField('metatestmock')->getValue()); + } + protected function assertStructure(array $result, $full = true) { $expectedKeys = [ diff --git a/tests/API/CustomerControllerTest.php b/tests/API/CustomerControllerTest.php index bb3d557f..5714e741 100644 --- a/tests/API/CustomerControllerTest.php +++ b/tests/API/CustomerControllerTest.php @@ -9,7 +9,9 @@ namespace App\Tests\API; +use App\Entity\Customer; use App\Entity\User; +use App\Tests\Mocks\CustomerTestMetaFieldSubscriberMock; use Symfony\Component\HttpFoundation\Response; /** @@ -161,6 +163,54 @@ class CustomerControllerTest extends APIControllerBaseTest $this->assertApiCallValidationError($response, ['currency']); } + public function testMetaActionThrowsNotFound() + { + $this->assertEntityNotFoundForPatch(User::ROLE_ADMIN, '/api/customers/42/meta', []); + } + + public function testMetaActionThrowsExceptionOnMissingName() + { + return $this->assertExceptionForPatchAction(User::ROLE_ADMIN, '/api/customers/1/meta', ['value' => 'X'], [ + 'code' => 400, + 'message' => 'Parameter "name" of value "NULL" violated a constraint "This value should not be null."' + ]); + } + + public function testMetaActionThrowsExceptionOnMissingValue() + { + return $this->assertExceptionForPatchAction(User::ROLE_ADMIN, '/api/customers/1/meta', ['name' => 'X'], [ + 'code' => 400, + 'message' => 'Parameter "value" of value "NULL" violated a constraint "This value should not be null."' + ]); + } + + public function testMetaActionThrowsExceptionOnMissingMetafield() + { + return $this->assertExceptionForPatchAction(User::ROLE_ADMIN, '/api/customers/1/meta', ['name' => 'X', 'value' => 'Y'], [ + 'code' => 500, + 'message' => 'Unknown meta-field requested' + ]); + } + + public function testMetaAction() + { + $client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN); + $client->getContainer()->get('event_dispatcher')->addSubscriber(new CustomerTestMetaFieldSubscriberMock()); + + $data = [ + 'name' => 'metatestmock', + 'value' => 'another,testing,bar' + ]; + $this->request($client, '/api/customers/1/meta', 'PATCH', [], json_encode($data)); + + $this->assertTrue($client->getResponse()->isSuccessful()); + + $em = $client->getContainer()->get('doctrine.orm.entity_manager'); + /** @var Customer $customer */ + $customer = $em->getRepository(Customer::class)->find(1); + $this->assertEquals('another,testing,bar', $customer->getMetaField('metatestmock')->getValue()); + } + protected function assertStructure(array $result, $full = true) { $expectedKeys = [ diff --git a/tests/API/ProjectControllerTest.php b/tests/API/ProjectControllerTest.php index 95ea49b5..d4203282 100644 --- a/tests/API/ProjectControllerTest.php +++ b/tests/API/ProjectControllerTest.php @@ -13,6 +13,7 @@ use App\Entity\Customer; use App\Entity\Project; use App\Entity\User; use App\Repository\Query\VisibilityQuery; +use App\Tests\Mocks\ProjectTestMetaFieldSubscriberMock; use Symfony\Bundle\FrameworkBundle\Client; use Symfony\Component\HttpFoundation\Response; @@ -211,6 +212,54 @@ class ProjectControllerTest extends APIControllerBaseTest $this->assertApiCallValidationError($response, ['customer']); } + public function testMetaActionThrowsNotFound() + { + $this->assertEntityNotFoundForPatch(User::ROLE_ADMIN, '/api/projects/42/meta', []); + } + + public function testMetaActionThrowsExceptionOnMissingName() + { + return $this->assertExceptionForPatchAction(User::ROLE_ADMIN, '/api/projects/1/meta', ['value' => 'X'], [ + 'code' => 400, + 'message' => 'Parameter "name" of value "NULL" violated a constraint "This value should not be null."' + ]); + } + + public function testMetaActionThrowsExceptionOnMissingValue() + { + return $this->assertExceptionForPatchAction(User::ROLE_ADMIN, '/api/projects/1/meta', ['name' => 'X'], [ + 'code' => 400, + 'message' => 'Parameter "value" of value "NULL" violated a constraint "This value should not be null."' + ]); + } + + public function testMetaActionThrowsExceptionOnMissingMetafield() + { + return $this->assertExceptionForPatchAction(User::ROLE_ADMIN, '/api/projects/1/meta', ['name' => 'X', 'value' => 'Y'], [ + 'code' => 500, + 'message' => 'Unknown meta-field requested' + ]); + } + + public function testMetaAction() + { + $client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN); + $client->getContainer()->get('event_dispatcher')->addSubscriber(new ProjectTestMetaFieldSubscriberMock()); + + $data = [ + 'name' => 'metatestmock', + 'value' => 'another,testing,bar' + ]; + $this->request($client, '/api/projects/1/meta', 'PATCH', [], json_encode($data)); + + $this->assertTrue($client->getResponse()->isSuccessful()); + + $em = $client->getContainer()->get('doctrine.orm.entity_manager'); + /** @var Project $project */ + $project = $em->getRepository(Project::class)->find(1); + $this->assertEquals('another,testing,bar', $project->getMetaField('metatestmock')->getValue()); + } + protected function assertStructure(array $result, $full = true) { $expectedKeys = [ diff --git a/tests/API/TimesheetControllerTest.php b/tests/API/TimesheetControllerTest.php index a0e10332..2d02fac0 100644 --- a/tests/API/TimesheetControllerTest.php +++ b/tests/API/TimesheetControllerTest.php @@ -18,6 +18,7 @@ use App\Entity\TimesheetMeta; use App\Entity\User; use App\Tests\DataFixtures\TimesheetFixtures; use App\Tests\Mocks\Security\UserDateTimeFactoryFactory; +use App\Tests\Mocks\TimesheetTestMetaFieldSubscriberMock; use App\Timesheet\UserDateTimeFactory; use Symfony\Component\HttpFoundation\Response; @@ -619,7 +620,7 @@ class TimesheetControllerTest extends APIControllerBaseTest public function testStopThrowsNotFound() { - $this->assertEntityNotFound(User::ROLE_USER, '/api/timesheets/11/stop', 'PATCH'); + $this->assertEntityNotFoundForPatch(User::ROLE_USER, '/api/timesheets/11/stop', []); } public function testStopNotAllowedForUser() @@ -782,7 +783,7 @@ class TimesheetControllerTest extends APIControllerBaseTest public function testRestartThrowsNotFound() { - $this->assertEntityNotFound(User::ROLE_USER, '/api/timesheets/42/restart', 'PATCH'); + $this->assertEntityNotFoundForPatch(User::ROLE_USER, '/api/timesheets/42/restart', []); } public function testExportAction() @@ -821,7 +822,55 @@ class TimesheetControllerTest extends APIControllerBaseTest public function testExportThrowsNotFound() { - $this->assertEntityNotFound(User::ROLE_ADMIN, '/api/timesheets/42/export', 'PATCH'); + $this->assertEntityNotFoundForPatch(User::ROLE_ADMIN, '/api/timesheets/42/export', []); + } + + public function testMetaActionThrowsNotFound() + { + $this->assertEntityNotFoundForPatch(User::ROLE_ADMIN, '/api/timesheets/42/meta', []); + } + + public function testMetaActionThrowsExceptionOnMissingName() + { + return $this->assertExceptionForPatchAction(User::ROLE_ADMIN, '/api/timesheets/1/meta', ['value' => 'X'], [ + 'code' => 400, + 'message' => 'Parameter "name" of value "NULL" violated a constraint "This value should not be null."' + ]); + } + + public function testMetaActionThrowsExceptionOnMissingValue() + { + return $this->assertExceptionForPatchAction(User::ROLE_ADMIN, '/api/timesheets/1/meta', ['name' => 'X'], [ + 'code' => 400, + 'message' => 'Parameter "value" of value "NULL" violated a constraint "This value should not be null."' + ]); + } + + public function testMetaActionThrowsExceptionOnMissingMetafield() + { + return $this->assertExceptionForPatchAction(User::ROLE_ADMIN, '/api/timesheets/1/meta', ['name' => 'X', 'value' => 'Y'], [ + 'code' => 500, + 'message' => 'Unknown meta-field requested' + ]); + } + + public function testMetaAction() + { + $client = $this->getClientForAuthenticatedUser(User::ROLE_USER); + $client->getContainer()->get('event_dispatcher')->addSubscriber(new TimesheetTestMetaFieldSubscriberMock()); + + $data = [ + 'name' => 'metatestmock', + 'value' => 'another,testing,bar' + ]; + $this->request($client, '/api/timesheets/1/meta', 'PATCH', [], json_encode($data)); + + $this->assertTrue($client->getResponse()->isSuccessful()); + + $em = $client->getContainer()->get('doctrine.orm.entity_manager'); + /** @var Timesheet $timesheet */ + $timesheet = $em->getRepository(Timesheet::class)->find(1); + $this->assertEquals('another,testing,bar', $timesheet->getMetaField('metatestmock')->getValue()); } protected function assertDefaultStructure(array $result, $full = true)