From acdf703d806b0d67d066cd72c36bd416ff729d74 Mon Sep 17 00:00:00 2001 From: Yisroel Baum Date: Sat, 2 May 2026 21:42:51 +0300 Subject: [PATCH] scope text endpoints by ownership TextRepository gains findByUser; JsonTextRepository and the fake implement filtering by stored userId. TextController splits the list endpoint into getMyTexts (own) and getAllTexts (admin), and getText now requires the session user, returning 403 to non-owners while admins bypass. --- app/Text/JsonTextRepository.php | 23 +++++++++++++ app/Text/TextController.php | 55 ++++++++++++++++++++++++++++-- app/Text/TextRepository.php | 6 ++++ tests/Fakes/FakeTextRepository.php | 26 ++++++++++++++ 4 files changed, 107 insertions(+), 3 deletions(-) diff --git a/app/Text/JsonTextRepository.php b/app/Text/JsonTextRepository.php index 23c612d..22fa42e 100644 --- a/app/Text/JsonTextRepository.php +++ b/app/Text/JsonTextRepository.php @@ -5,6 +5,7 @@ namespace App\Text; use App\Text\Text; use App\Text\CreateTextDto; use App\Text\TextRepository; +use App\User\User; use App\User\UserRepository; use DomainException; @@ -67,6 +68,28 @@ class JsonTextRepository implements TextRepository ); } + /** + * @return Text[] + */ + public function findByUser(User $user): array + { + $texts = $this->readTexts(); + $userId = $user->getId(); + $owned = array_filter( + $texts, + function (array $data) use ($userId) { + return $data['userId'] === $userId; + } + ); + + return array_map( + function (array $data) { + return $this->hydrate($data); + }, + array_values($owned) + ); + } + private function hydrate(array $data): Text { $user = $this->userRepo->find($data['userId']); diff --git a/app/Text/TextController.php b/app/Text/TextController.php index 14230e1..9e88955 100644 --- a/app/Text/TextController.php +++ b/app/Text/TextController.php @@ -16,7 +16,7 @@ class TextController private TextRepository $textRepository, ) {} - public function getTexts(Response $response): Response + public function getAllTexts(Response $response): Response { $texts = $this->textRepository->getAll(); @@ -31,14 +31,63 @@ class TextController return $response->withHeader('Content-Type', 'application/json'); } - public function getText(Response $response, int $textId): Response - { + public function getMyTexts( + Request $request, + Response $response, + ): Response { + $user = $request->getAttribute('user'); + if (!$user instanceof User) { + return $this->errorResponse( + $response, + 401, + 'unauthenticated' + ); + } + + $texts = $this->textRepository->findByUser($user); + + $data = array_map(function ($text) { + return [ + 'id' => $text->getId(), + 'name' => $text->getName(), + ]; + }, $texts); + + $response->getBody()->write(json_encode($data)); + return $response->withHeader('Content-Type', 'application/json'); + } + + public function getText( + Request $request, + Response $response, + int $textId, + ): Response { + $user = $request->getAttribute('user'); + if (!$user instanceof User) { + return $this->errorResponse( + $response, + 401, + 'unauthenticated' + ); + } + $text = $this->textRepository->find($textId); if ($text === null) { return $response->withStatus(404); } + if ( + $text->getUser()->getId() !== $user->getId() + && !$user->isAdmin() + ) { + return $this->errorResponse( + $response, + 403, + 'forbidden' + ); + } + $response->getBody()->write(json_encode([ 'id' => $text->getId(), 'name' => $text->getName(), diff --git a/app/Text/TextRepository.php b/app/Text/TextRepository.php index d899bcf..88dabce 100644 --- a/app/Text/TextRepository.php +++ b/app/Text/TextRepository.php @@ -4,6 +4,7 @@ namespace App\Text; use App\Text\Text; use App\Text\CreateTextDto; +use App\User\User; interface TextRepository { @@ -15,4 +16,9 @@ interface TextRepository * @return Text[] */ public function getAll(): array; + + /** + * @return Text[] + */ + public function findByUser(User $user): array; } diff --git a/tests/Fakes/FakeTextRepository.php b/tests/Fakes/FakeTextRepository.php index 3e556a2..35ee9f4 100644 --- a/tests/Fakes/FakeTextRepository.php +++ b/tests/Fakes/FakeTextRepository.php @@ -5,6 +5,7 @@ namespace Tests\Fakes; use App\Text\CreateTextDto; use App\Text\Text; use App\Text\TextRepository; +use App\User\User; class FakeTextRepository implements TextRepository { @@ -61,4 +62,29 @@ class FakeTextRepository implements TextRepository array_values($this->existingTexts) ); } + + /** + * @return Text[] + */ + public function findByUser(User $user): array + { + $userId = $user->getId(); + $owned = array_filter( + $this->existingTexts, + function (Text $text) use ($userId) { + return $text->getUser()->getId() === $userId; + } + ); + + return array_map( + function (Text $text) { + return new Text( + id: $text->getId(), + name: $text->getName(), + user: $text->getUser(), + ); + }, + array_values($owned) + ); + } }