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);