From 59d4ed88c4696b4316088ce46d5d27a5498330ab Mon Sep 17 00:00:00 2001 From: Yisroel Baum Date: Wed, 6 May 2026 22:26:35 +0300 Subject: [PATCH] implement post and comment controllers Wires PostController (recent, show, create, delete, listByUser) and CommentController (listForPost, create, delete) to the existing use cases. Posts and comments expose author display names alongside user IDs. CommentRepository binding added to RepositoryServiceProvider. --- backend/app/Controllers/CommentController.php | 124 ++++++++++++ backend/app/Controllers/PostController.php | 181 ++++++++++++++++++ .../Providers/RepositoryServiceProvider.php | 6 + backend/routes/api.php | 31 +++ 4 files changed, 342 insertions(+) create mode 100644 backend/app/Controllers/CommentController.php create mode 100644 backend/app/Controllers/PostController.php diff --git a/backend/app/Controllers/CommentController.php b/backend/app/Controllers/CommentController.php new file mode 100644 index 0000000..0b4433b --- /dev/null +++ b/backend/app/Controllers/CommentController.php @@ -0,0 +1,124 @@ +listCommentsForPost->execute( + new ListCommentsForPostRequest(postId: $postId), + ); + } catch (BadRequestException $exception) { + return new JsonResponse( + ['error' => $exception->getMessage()], 400, + ); + } + + return new JsonResponse([ + 'comments' => array_map( + function (Comment $comment) { + return $this->serialize($comment); + }, + $comments, + ), + ], 200); + } + + public function create(Request $request, int $postId): JsonResponse + { + /** @var User $user */ + $user = $request->attributes->get('user'); + try { + $comment = $this->createComment->execute(new CreateCommentRequest( + postId: $postId, + userId: $user->getId(), + body: $request->input('body'), + )); + } catch (BadRequestException $exception) { + return new JsonResponse( + ['error' => $exception->getMessage()], 400, + ); + } catch (DomainException $exception) { + return new JsonResponse( + ['error' => $exception->getMessage()], 404, + ); + } + + return new JsonResponse([ + 'comment' => $this->serialize($comment), + ], 201); + } + + public function delete(Request $request, int $id): JsonResponse + { + /** @var User $user */ + $user = $request->attributes->get('user'); + try { + $this->deleteComment->execute(new DeleteCommentRequest( + commentId: $id, + requesterId: $user->getId(), + requesterIsAdmin: $user->isAdmin(), + )); + } catch (BadRequestException $exception) { + return new JsonResponse( + ['error' => $exception->getMessage()], 400, + ); + } catch (ForbiddenException $exception) { + return new JsonResponse( + ['error' => $exception->getMessage()], 403, + ); + } + + return new JsonResponse(null, 204); + } + + /** + * @return array{ + * id: int, + * postId: int, + * userId: int, + * authorDisplayName: string, + * body: string, + * createdAt: string + * } + */ + private function serialize(Comment $comment): array + { + $author = $this->userRepo->find($comment->getUserId()); + + return [ + 'id' => $comment->getId(), + 'postId' => $comment->getPostId(), + 'userId' => $comment->getUserId(), + 'authorDisplayName' => $author === null + ? '' + : $author->getDisplayName(), + 'body' => $comment->getBody(), + 'createdAt' => $comment->getCreatedAt()->format(DATE_ATOM), + ]; + } +} diff --git a/backend/app/Controllers/PostController.php b/backend/app/Controllers/PostController.php new file mode 100644 index 0000000..a01429c --- /dev/null +++ b/backend/app/Controllers/PostController.php @@ -0,0 +1,181 @@ +listRecentPosts->execute( + new ListRecentPostsRequest(limit: self::RECENT_LIMIT), + ); + } catch (BadRequestException $exception) { + return new JsonResponse( + ['error' => $exception->getMessage()], 400, + ); + } + + return new JsonResponse([ + 'posts' => array_map( + function (Post $post) { + return $this->serialize($post); + }, + $posts, + ), + ], 200); + } + + public function show(Request $request, int $id): JsonResponse + { + try { + $post = $this->getPost->execute($id); + } catch (BadRequestException $exception) { + return new JsonResponse( + ['error' => $exception->getMessage()], 400, + ); + } + if ($post === null) { + return new JsonResponse(['error' => 'post not found'], 404); + } + + return new JsonResponse([ + 'post' => $this->serialize($post), + ], 200); + } + + public function listByUser( + Request $request, + string $displayName, + ): JsonResponse { + $user = $this->userRepo->findByDisplayName($displayName); + if ($user === null) { + return new JsonResponse(['error' => 'user not found'], 404); + } + try { + $posts = $this->listUserPosts->execute( + new ListUserPostsRequest(userId: $user->getId()), + ); + } catch (BadRequestException $exception) { + return new JsonResponse( + ['error' => $exception->getMessage()], 400, + ); + } + + return new JsonResponse([ + 'user' => [ + 'id' => $user->getId(), + 'displayName' => $user->getDisplayName(), + ], + 'posts' => array_map( + function (Post $post) { + return $this->serialize($post); + }, + $posts, + ), + ], 200); + } + + public function create(Request $request): JsonResponse + { + /** @var User $user */ + $user = $request->attributes->get('user'); + try { + $post = $this->createPost->execute(new CreatePostRequest( + userId: $user->getId(), + title: $request->input('title'), + body: $request->input('body'), + )); + } catch (BadRequestException $exception) { + return new JsonResponse( + ['error' => $exception->getMessage()], 400, + ); + } + + return new JsonResponse([ + 'post' => $this->serialize($post), + ], 201); + } + + public function delete(Request $request, int $id): JsonResponse + { + /** @var User $user */ + $user = $request->attributes->get('user'); + try { + $this->deletePost->execute(new DeletePostRequest( + postId: $id, + requesterId: $user->getId(), + requesterIsAdmin: $user->isAdmin(), + )); + } catch (BadRequestException $exception) { + return new JsonResponse( + ['error' => $exception->getMessage()], 400, + ); + } catch (ForbiddenException $exception) { + return new JsonResponse( + ['error' => $exception->getMessage()], 403, + ); + } catch (DomainException $exception) { + return new JsonResponse( + ['error' => $exception->getMessage()], 409, + ); + } + + return new JsonResponse(null, 204); + } + + /** + * @return array{ + * id: int, + * userId: int, + * authorDisplayName: string, + * title: string, + * body: string, + * createdAt: string + * } + */ + private function serialize(Post $post): array + { + $author = $this->userRepo->find($post->getUserId()); + + return [ + 'id' => $post->getId(), + 'userId' => $post->getUserId(), + 'authorDisplayName' => $author === null + ? '' + : $author->getDisplayName(), + 'title' => $post->getTitle(), + 'body' => $post->getBody(), + 'createdAt' => $post->getCreatedAt()->format(DATE_ATOM), + ]; + } +} diff --git a/backend/app/Providers/RepositoryServiceProvider.php b/backend/app/Providers/RepositoryServiceProvider.php index 1532e12..743fe1e 100644 --- a/backend/app/Providers/RepositoryServiceProvider.php +++ b/backend/app/Providers/RepositoryServiceProvider.php @@ -4,6 +4,8 @@ namespace App\Providers; use App\Auth\EloquentSessionRepository; use App\Auth\SessionRepository; +use App\Comment\CommentRepository; +use App\Comment\EloquentCommentRepository; use App\Email\EmailConfirmationToken\EloquentEmailConfirmationTokenRepository; use App\Email\EmailConfirmationToken\EmailConfirmationTokenRepository; use App\Post\EloquentPostRepository; @@ -32,5 +34,9 @@ class RepositoryServiceProvider extends ServiceProvider PostRepository::class, EloquentPostRepository::class, ); + $this->app->bind( + CommentRepository::class, + EloquentCommentRepository::class, + ); } } diff --git a/backend/routes/api.php b/backend/routes/api.php index 45699fb..f614658 100644 --- a/backend/routes/api.php +++ b/backend/routes/api.php @@ -1,6 +1,8 @@ middleware(AuthMiddleware::class); Route::get('/me', [AuthController::class, 'me']) ->middleware(AuthMiddleware::class); + +Route::get('/posts', [PostController::class, 'recent']); +Route::get('/posts/{id}', [PostController::class, 'show']) + ->whereNumber('id'); +Route::post('/posts', [PostController::class, 'create']) + ->middleware(AuthMiddleware::class); +Route::delete('/posts/{id}', [PostController::class, 'delete']) + ->whereNumber('id') + ->middleware(AuthMiddleware::class); + +Route::get( + '/users/{displayName}/posts', + [PostController::class, 'listByUser'], +); + +Route::get( + '/posts/{postId}/comments', + [CommentController::class, 'listForPost'], +)->whereNumber('postId'); +Route::post( + '/posts/{postId}/comments', + [CommentController::class, 'create'], +)->whereNumber('postId') + ->middleware(AuthMiddleware::class); +Route::delete( + '/comments/{id}', + [CommentController::class, 'delete'], +)->whereNumber('id') + ->middleware(AuthMiddleware::class);