canReceiveCommentsFrom($actor)) { throw ValidationException::withMessages([ 'card' => 'Comments are unavailable for this card.', ]); } $comment = NovaCardComment::query()->create([ 'card_id' => $card->id, 'user_id' => $actor->id, 'parent_id' => $parent?->id, 'body' => trim($body), 'rendered_body' => nl2br(e(trim($body))), 'status' => 'visible', ]); if (! $card->isOwnedBy($actor)) { $this->notifications->notifyNovaCardComment($card->user, $actor, $card, $comment); } UpdateNovaCardStatsJob::dispatch($card->id); return $comment->fresh(['user.profile', 'replies.user.profile', 'card.user']); } public function delete(NovaCardComment $comment, User $actor): void { if ((int) $comment->user_id !== (int) $actor->id && ! $comment->card->isOwnedBy($actor) && ! $this->isModerator($actor)) { throw ValidationException::withMessages([ 'comment' => 'You are not allowed to remove this comment.', ]); } if ($comment->trashed()) { return; } $comment->delete(); UpdateNovaCardStatsJob::dispatch($comment->card_id); } public function mapComments(NovaCard $card, ?User $viewer = null): array { $comments = $card->comments() ->whereNull('parent_id') ->where('status', 'visible') ->with(['user.profile', 'replies.user.profile', 'card.user']) ->latest() ->limit(30) ->get(); return $comments->map(fn (NovaCardComment $comment) => $this->mapComment($comment, $viewer))->all(); } private function mapComment(NovaCardComment $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->card->isOwnedBy($viewer) || $this->isModerator($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, 'avatar_url' => AvatarUrl::forUser((int) $user->id, $user->profile?->avatar_hash, 64), 'profile_url' => '/@' . Str::lower((string) $user->username), ], 'replies' => $comment->replies ->where('status', 'visible') ->map(fn (NovaCardComment $reply) => $this->mapComment($reply, $viewer)) ->values() ->all(), ]; } private function isModerator(User $user): bool { if (method_exists($user, 'isModerator')) { return (bool) $user->isModerator(); } if (method_exists($user, 'hasRole')) { return (bool) $user->hasRole('moderator') || (bool) $user->hasRole('admin'); } return false; } }