diff --git a/src/Controller/SurfSession/NewSurfSessionController.php b/src/Controller/SurfSession/NewSurfSessionController.php index 1c5ca4b..e6caf8e 100644 --- a/src/Controller/SurfSession/NewSurfSessionController.php +++ b/src/Controller/SurfSession/NewSurfSessionController.php @@ -8,14 +8,17 @@ use App\Entity\SurfSession; use App\Entity\User; use App\Enum\User\UserRole; -use App\Form\Model\SurfSession\SurfSessionWriteModel; use App\Form\SurfSession\SurfSessionFormType; use App\Repository\SurfSessionRepository; +use App\Security\Voter\TripVoter; +use App\Service\SurfSession\SurfSessionWriteModelFactory; +use App\Service\Trip\TripReadModelProvider; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\ObjectMapper\ObjectMapperInterface; use Symfony\Component\Routing\Attribute\Route; +use Symfony\Component\Routing\Requirement\Requirement; use Symfony\Component\Security\Http\Attribute\CurrentUser; use Symfony\Component\Security\Http\Attribute\IsGranted; @@ -25,6 +28,8 @@ public function __construct( private readonly ObjectMapperInterface $objectMapper, private readonly SurfSessionRepository $surfSessionRepository, private readonly SurfSessionCacheInvalidator $surfSessionCacheInvalidator, + private readonly TripReadModelProvider $tripReadModelProvider, + private readonly SurfSessionWriteModelFactory $surfSessionWriteModelFactory, ) {} #[Route( @@ -32,10 +37,24 @@ public function __construct( name: 'app.surf_session.new', methods: [Request::METHOD_GET, Request::METHOD_POST], )] + #[Route( + path: '/trip/{tripId}/sessions/new', + name: 'app.trip.surf_session.new', + requirements: ['tripId' => Requirement::POSITIVE_INT], + methods: [Request::METHOD_GET, Request::METHOD_POST], + )] #[IsGranted(UserRole::USER)] - public function __invoke(Request $request, #[CurrentUser()] User $currentUser): Response + public function __invoke(Request $request, #[CurrentUser()] User $currentUser, ?int $tripId = null): Response { - $surfSessionWriteModel = new SurfSessionWriteModel(); + $trip = null; + + if (null !== $tripId) { + $trip = $this->tripReadModelProvider->getById($tripId); + + $this->denyAccessUnlessGranted(TripVoter::EDIT, $trip); + } + + $surfSessionWriteModel = $this->surfSessionWriteModelFactory->create($trip); $form = $this->createForm(SurfSessionFormType::class, $surfSessionWriteModel); $form->handleRequest($request); diff --git a/src/Controller/Trip/EditTripController.php b/src/Controller/Trip/EditTripController.php index 64a56e6..56c39c5 100644 --- a/src/Controller/Trip/EditTripController.php +++ b/src/Controller/Trip/EditTripController.php @@ -4,7 +4,6 @@ namespace App\Controller\Trip; -use App\Exception\TripNotFoundHttpException; use App\Form\Model\Trip\TripWriteModel; use App\Form\Trip\TripFormType; use App\Security\Voter\TripVoter; @@ -40,10 +39,6 @@ public function __invoke(Request $request, int $id, string $slug): Response { $trip = $this->tripReadModelProvider->getById($id); - if (null === $trip) { - throw new TripNotFoundHttpException($id); - } - $this->denyAccessUnlessGranted(TripVoter::EDIT, $trip); if ($trip->slug->value !== $slug) { diff --git a/src/Controller/Trip/ShowTripController.php b/src/Controller/Trip/ShowTripController.php index 47f0c39..6c76981 100644 --- a/src/Controller/Trip/ShowTripController.php +++ b/src/Controller/Trip/ShowTripController.php @@ -4,7 +4,6 @@ namespace App\Controller\Trip; -use App\Exception\TripNotFoundHttpException; use App\Service\Trip\TripReadModelProvider; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; @@ -33,10 +32,6 @@ public function __invoke(int $id, string $slug): Response { $trip = $this->tripReadModelProvider->getById($id); - if (null === $trip) { - throw new TripNotFoundHttpException($id); - } - if ($trip->slug->value !== $slug) { return $this->redirectToRoute(self::ROUTE, [ 'id' => $trip->id, diff --git a/src/Service/SurfSession/SurfSessionWriteModelFactory.php b/src/Service/SurfSession/SurfSessionWriteModelFactory.php new file mode 100644 index 0000000..33ee169 --- /dev/null +++ b/src/Service/SurfSession/SurfSessionWriteModelFactory.php @@ -0,0 +1,50 @@ +resolveStartAt($trip); + $tripSelect = null; + + if (null !== $trip) { + $tripSelect = new TripSelectReadModel( + id: $trip->id, + title: $trip->title->value, + location: $trip->location->value, + ); + } + + $surfSessionWriteModel->trip = $tripSelect; + $surfSessionWriteModel->spot = $tripSelect?->location; + $surfSessionWriteModel->startAt = $startAt; + $surfSessionWriteModel->endAt = $startAt->modify(sprintf('+%d hours', self::DEFAULT_SESSION_DURATION_HOURS)); + + return $surfSessionWriteModel; + } + + private function resolveStartAt(?AbstractTripReadModel $trip = null): \DateTimeImmutable + { + $now = new \DateTimeImmutable(); + + $startAt = match (true) { + null === $trip => $now, + $now < $trip->startAt => $trip->startAt, + $now > $trip->endAt => $trip->endAt, + default => $now, + }; + + return $startAt->setTime(10, 0); + } +} diff --git a/src/Service/Trip/TripReadModelProvider.php b/src/Service/Trip/TripReadModelProvider.php index d4381c3..628c0f4 100644 --- a/src/Service/Trip/TripReadModelProvider.php +++ b/src/Service/Trip/TripReadModelProvider.php @@ -5,6 +5,7 @@ namespace App\Service\Trip; use App\Cache\Trip\TripCacheKeys; +use App\Exception\TripNotFoundHttpException; use App\ReadModel\Trip\TripShowReadModel; use App\Repository\TripRepository; use Symfony\Contracts\Cache\ItemInterface; @@ -19,9 +20,9 @@ public function __construct( private TagAwareCacheInterface $cache, ) {} - public function getById(int $id): ?TripShowReadModel + public function getById(int $id): TripShowReadModel { - return $this->cache->get( + $trip = $this->cache->get( TripCacheKeys::readModel($id), function (ItemInterface $item) use ($id): ?TripShowReadModel { $item->expiresAfter(new \DateInterval(self::CACHE_TTL)); @@ -29,5 +30,13 @@ function (ItemInterface $item) use ($id): ?TripShowReadModel { return $this->tripRepository->findShowReadModelById($id); }, ); + + if (null === $trip) { + $this->cache->delete(TripCacheKeys::readModel($id)); + + throw new TripNotFoundHttpException($id); + } + + return $trip; } } diff --git a/templates/trip/edit.html.twig b/templates/trip/edit.html.twig index 0f2e658..16f807f 100644 --- a/templates/trip/edit.html.twig +++ b/templates/trip/edit.html.twig @@ -3,7 +3,16 @@ {% set title = 'trip.edit.label'|trans %} {% block body %} - +
+ + {% if is_granted('EDIT', trip) %} + + {% endif %} +
+ diff --git a/templates/trip/show.html.twig b/templates/trip/show.html.twig index b722a70..8c466b9 100644 --- a/templates/trip/show.html.twig +++ b/templates/trip/show.html.twig @@ -1,16 +1,26 @@ {% extends 'base.html.twig' %} {% set title = 'trip.label'|trans %} +{% set is_granted_edit = is_granted('EDIT', trip) %} {% block body %} - +
+ + {% if is_granted_edit %} + + {% endif %} +
+ - {% if is_granted('EDIT', trip) %} + {% if is_granted_edit %} {% endif %}
- {% if is_granted('EDIT', trip) %} + {% if is_granted_edit %} {% endif %} diff --git a/tests/Functional/Controller/SurfSession/NewSurfSessionControllerTest.php b/tests/Functional/Controller/SurfSession/NewSurfSessionControllerTest.php index 4f4cbe4..d7faea6 100644 --- a/tests/Functional/Controller/SurfSession/NewSurfSessionControllerTest.php +++ b/tests/Functional/Controller/SurfSession/NewSurfSessionControllerTest.php @@ -23,6 +23,7 @@ final class NewSurfSessionControllerTest extends CustomWebTestCase // Paths private const string PATH_INDEX = '/en/sessions'; private const string PATH_NEW = '/en/sessions/new'; + private const string PATH_NEW_FROM_TRIP = '/en/trip/%d/sessions/new'; // Selectors private const string FORM = 'form[name="surf_session"]'; private const string FIRST_CARD = '.app-card'; @@ -89,4 +90,33 @@ public function testCreateSurfSessionIsSuccessful(): void $this->assertSelectorTextContains(self::FIRST_CARD, self::SESSION_SPOT); $this->assertSelectorTextContains(self::FIRST_CARD, self::SESSION_BOARD); } + + public function testNewSurfSessionWithTrip(): void + { + $trip = TripFactory::find(['title' => Title::from(TripStory::CURRENT_TRIP_TITLE)]); + $now = new \DateTimeImmutable()->setTime(10, 0); + + $this->client->request(Request::METHOD_GET, sprintf(self::PATH_NEW_FROM_TRIP, $trip->id)); + + $startAt = $this->parseDateTimeFromInput('#surf_session_startAt'); + $endAt = $this->parseDateTimeFromInput('#surf_session_endAt'); + + $this->assertResponseIsSuccessful(); + $this->assertSame($trip->location->value, $this->getInputValue('#surf_session_spot')); + $this->assertSame((string) $trip->id, $this->getInputValue('#surf_session_trip option[selected]')); + $this->assertSame($now->format('Y-m-d\TH'), $startAt->format('Y-m-d\TH')); + $this->assertSame($startAt->modify('+2 hours')->format('Y-m-d\TH'), $endAt->format('Y-m-d\TH')); + } + + private function parseDateTimeFromInput(string $selector): \DateTimeImmutable + { + $value = $this->getInputValue($selector); + + return \DateTimeImmutable::createFromFormat('Y-m-d\TH:i', $value); + } + + private function getInputValue(string $selector): string + { + return (string) $this->client->getCrawler()->filter($selector)->attr('value'); + } } diff --git a/tests/Functional/Controller/Trip/ShowTripControllerTest.php b/tests/Functional/Controller/Trip/ShowTripControllerTest.php index bffca5f..2c4e575 100644 --- a/tests/Functional/Controller/Trip/ShowTripControllerTest.php +++ b/tests/Functional/Controller/Trip/ShowTripControllerTest.php @@ -24,6 +24,7 @@ final class ShowTripControllerTest extends CustomWebTestCase private const string PATH = '/en/trip/%d/%s'; private const string TITLE = 'Trip'; + private const string ADD_SESSION_LABEL = 'Add session'; private ?Trip $trip = null; @@ -45,6 +46,7 @@ public function testShowTripPageIsDisplayed(): void $this->assertSelectorTextSame(self::TITLE_H1, self::TITLE); $this->assertSelectorExists(self::TABLE); $this->assertSelectorTextContains(self::TABLE, $this->trip->title->value); + $this->assertCount(1, $this->client->getCrawler()->selectLink(self::ADD_SESSION_LABEL)); } public function testShowTripPageRedirectsWhenSlugIsInvalid(): void diff --git a/translations/messages+intl-icu.en.yaml b/translations/messages+intl-icu.en.yaml index 0b3f55e..509fa39 100644 --- a/translations/messages+intl-icu.en.yaml +++ b/translations/messages+intl-icu.en.yaml @@ -103,6 +103,7 @@ surf_session: created_successfully: The session has been created. updated_successfully: The session has been updated. deleted_successfully: The session has been deleted. + add.label: Add session new.label: New session edit.label: Edit session delete.confirm: Are you sure you want to delete this session? diff --git a/translations/messages+intl-icu.fr.yaml b/translations/messages+intl-icu.fr.yaml index 79cb17f..fa115b1 100644 --- a/translations/messages+intl-icu.fr.yaml +++ b/translations/messages+intl-icu.fr.yaml @@ -103,6 +103,7 @@ surf_session: created_successfully: La session a été créée. updated_successfully: La session a été mise à jour. deleted_successfully: La session a été supprimée. + add.label: Ajouter une session new.label: Nouvelle session edit.label: Édition de la session delete.confirm: Es-tu sûr de vouloir supprimer cette session ?