From 93da08756cd3eeb7eef6575ffce8e31645b71a7d Mon Sep 17 00:00:00 2001 From: Yisroel Baum Date: Wed, 6 May 2026 22:14:11 +0300 Subject: [PATCH] add Comment persistence: model, migration, eloquent + fake repo --- backend/app/Comment/CommentModel.php | 43 ++++++++++ .../app/Comment/EloquentCommentRepository.php | 66 +++++++++++++++ ...026_05_06_000004_create_comments_table.php | 28 +++++++ backend/tests/Fakes/FakeCommentRepository.php | 82 +++++++++++++++++++ 4 files changed, 219 insertions(+) create mode 100644 backend/app/Comment/CommentModel.php create mode 100644 backend/app/Comment/EloquentCommentRepository.php create mode 100644 backend/database/migrations/2026_05_06_000004_create_comments_table.php create mode 100644 backend/tests/Fakes/FakeCommentRepository.php diff --git a/backend/app/Comment/CommentModel.php b/backend/app/Comment/CommentModel.php new file mode 100644 index 0000000..8182d94 --- /dev/null +++ b/backend/app/Comment/CommentModel.php @@ -0,0 +1,43 @@ +|CommentModel newModelQuery() + * @method static Builder|CommentModel newQuery() + * @method static Builder|CommentModel query() + * @method static Builder|CommentModel whereId($value) + * @method static Builder|CommentModel wherePostId($value) + * @method static Builder|CommentModel whereUserId($value) + * @method static Builder|CommentModel whereBody($value) + * @method static Builder|CommentModel whereCreatedAt($value) + * + * @mixin \Eloquent + */ +class CommentModel extends Model +{ + protected $table = 'comments'; + + public $timestamps = false; + + protected $fillable = [ + 'post_id', + 'user_id', + 'body', + 'created_at', + ]; + + protected $casts = [ + 'created_at' => 'immutable_datetime', + ]; +} diff --git a/backend/app/Comment/EloquentCommentRepository.php b/backend/app/Comment/EloquentCommentRepository.php new file mode 100644 index 0000000..c2a3f5b --- /dev/null +++ b/backend/app/Comment/EloquentCommentRepository.php @@ -0,0 +1,66 @@ + $dto->postId, + 'user_id' => $dto->userId, + 'body' => $dto->body, + 'created_at' => $dto->createdAt, + ]); + + return $this->toDomain($model); + } + + public function find(int $id): ?Comment + { + $model = CommentModel::find($id); + + return $model === null ? null : $this->toDomain($model); + } + + /** + * @return Comment[] + */ + public function findByPostId(int $postId): array + { + $models = CommentModel::query() + ->where('post_id', $postId) + ->orderBy('created_at', 'asc') + ->get(); + + return $models->map( + function (CommentModel $model) { + return $this->toDomain($model); + }, + )->all(); + } + + public function delete(int $id): void + { + CommentModel::query()->where('id', $id)->delete(); + } + + private function toDomain(CommentModel $model): Comment + { + $utc = new DateTimeZone('UTC'); + + return new Comment( + id: $model->id, + postId: $model->post_id, + userId: $model->user_id, + body: $model->body, + createdAt: new DateTimeImmutable( + $model->created_at->toDateTimeString(), + $utc, + ), + ); + } +} diff --git a/backend/database/migrations/2026_05_06_000004_create_comments_table.php b/backend/database/migrations/2026_05_06_000004_create_comments_table.php new file mode 100644 index 0000000..eea8665 --- /dev/null +++ b/backend/database/migrations/2026_05_06_000004_create_comments_table.php @@ -0,0 +1,28 @@ +id(); + $table->foreignId('post_id') + ->constrained('posts') + ->cascadeOnDelete(); + $table->foreignId('user_id') + ->constrained('users') + ->cascadeOnDelete(); + $table->text('body'); + $table->dateTime('created_at')->index(); + }); + } + + public function down(): void + { + Schema::dropIfExists('comments'); + } +}; diff --git a/backend/tests/Fakes/FakeCommentRepository.php b/backend/tests/Fakes/FakeCommentRepository.php new file mode 100644 index 0000000..7286f2d --- /dev/null +++ b/backend/tests/Fakes/FakeCommentRepository.php @@ -0,0 +1,82 @@ +getNextId(); + $comment = new Comment( + id: $id, + postId: $dto->postId, + userId: $dto->userId, + body: $dto->body, + createdAt: $dto->createdAt, + ); + $this->existingComments[$id] = $comment; + + return $this->copy($comment); + } + + public function find(int $id): ?Comment + { + $comment = $this->existingComments[$id] ?? null; + if ($comment === null) { + return null; + } + + return $this->copy($comment); + } + + /** + * @return Comment[] + */ + public function findByPostId(int $postId): array + { + $matching = []; + foreach ($this->existingComments as $comment) { + if ($comment->getPostId() === $postId) { + $matching[] = $this->copy($comment); + } + } + usort( + $matching, + function (Comment $left, Comment $right) { + return $left->getCreatedAt() <=> $right->getCreatedAt(); + }, + ); + + return $matching; + } + + public function delete(int $id): void + { + unset($this->existingComments[$id]); + } + + private function copy(Comment $comment): Comment + { + return new Comment( + id: $comment->getId(), + postId: $comment->getPostId(), + userId: $comment->getUserId(), + body: $comment->getBody(), + createdAt: $comment->getCreatedAt(), + ); + } + + private function getNextId(): int + { + return count($this->existingComments) + 1; + } +}