canReceiveCommentsFrom($actor)) { throw ValidationException::withMessages([ 'collection' => 'Comments are disabled for this collection.', ]); } $comment = CollectionComment::query()->create([ 'collection_id' => $collection->id, 'user_id' => $actor->id, 'parent_id' => $parent?->id, 'body' => trim($body), 'rendered_body' => nl2br(e(trim($body))), 'status' => Collection::COMMENT_VISIBLE, ]); $collection->increment('comments_count'); $collection->forceFill(['last_activity_at' => now()])->save(); if (! $collection->isOwnedBy($actor)) { $this->notifications->notifyCollectionComment($collection->user, $actor, $collection, $comment); } return $comment->fresh(['user.profile', 'replies.user.profile']); } public function delete(CollectionComment $comment, User $actor): void { if ((int) $comment->user_id !== (int) $actor->id && ! $comment->collection->canBeManagedBy($actor)) { throw ValidationException::withMessages([ 'comment' => 'You are not allowed to remove this comment.', ]); } if ($comment->trashed()) { return; } $comment->delete(); $comment->collection()->decrement('comments_count'); } public function mapComments(Collection $collection, ?User $viewer = null): array { $comments = $collection->comments() ->whereNull('parent_id') ->where('status', Collection::COMMENT_VISIBLE) ->with(['user.profile', 'replies.user.profile']) ->latest() ->limit(30) ->get(); return $comments->map(fn (CollectionComment $comment) => $this->mapComment($comment, $viewer))->all(); } private function mapComment(CollectionComment $comment, ?User $viewer = null): array { $user = $comment->user; return [ 'id' => (int) $comment->id, 'body' => (string) $comment->body, 'rendered_content' => (string) $comment->rendered_body, 'time_ago' => $comment->created_at?->diffForHumans(), 'created_at' => $comment->created_at?->toISOString(), 'can_delete' => $viewer !== null && ((int) $viewer->id === (int) $comment->user_id || $comment->collection->canBeManagedBy($viewer)), 'can_report' => $viewer !== null && (int) $viewer->id !== (int) $comment->user_id, 'user' => [ 'id' => (int) $user->id, 'display' => (string) ($user->name ?: $user->username), 'username' => (string) $user->username, 'level' => (int) ($user->level ?? 0), 'rank' => (string) ($user->rank ?? ''), 'avatar_url' => AvatarUrl::forUser((int) $user->id, $user->profile?->avatar_hash, 64), 'profile_url' => '/@' . Str::lower((string) $user->username), ], 'replies' => $comment->replies ->where('status', Collection::COMMENT_VISIBLE) ->map(fn (CollectionComment $reply) => $this->mapComment($reply, $viewer)) ->values() ->all(), ]; } }