service call -> assign -> Response). * * @api */ class Dashboard { /** * @param CommentService $commentService Comment business logic (authorization-aware deletes) * @param CommentRepository $commentRepository Comment data access (replies, counts) * @param ReactionService $reactionsService Reaction business logic */ public function __construct( private CommentService $commentService, private CommentRepository $commentRepository, private ReactionService $reactionsService, ) {} /** * Gets the top-level comments for a project with their replies attached. * * Mirrors the legacy dashboard behavior: top-level comments (parent 0) * each enriched with a 'replies' key holding their direct replies. * * @param int $projectId The project to load comments for * @return array> Top-level comments, each with a 'replies' array * * @api */ public function getProjectCommentsWithReplies(int $projectId): array { if ($projectId <= 0) { throw new \InvalidArgumentException('A valid project id is required to load comments.'); } $comments = $this->commentService->getComments('project', $projectId, 0); if (! is_array($comments)) { return []; } return array_map(function ($comment) { $comment['replies'] = $this->commentRepository->getReplies($comment['id']); return $comment; }, $comments); } /** * Counts all comments attached to a project. * * @param int $projectId The project to count comments for * @return int The number of comments * * @api */ public function countProjectComments(int $projectId): int { if ($projectId <= 0) { throw new \InvalidArgumentException('A valid project id is required to count comments.'); } return (int) $this->commentRepository->countComments('project', $projectId); } /** * Deletes a project comment. * * Delegates to the comment service so its author/manager authorization * check is enforced (the legacy dashboard deleted directly with no check). * * @param int $commentId The comment to delete * @return bool True if the comment was deleted * * @api */ public function deleteProjectComment(int $commentId): bool { return $this->commentService->deleteComment($commentId); } /** * Adds a comment to a project. * * @param array $values The submitted comment values * @param int $projectId The project the comment belongs to * @param array $project The loaded project entity * @return bool True if the comment was created * * @api */ public function addProjectComment(array $values, int $projectId, array $project): bool { return $this->commentService->addComment($values, 'project', $projectId, $project); } /** * Determines whether a user has favorited a project. * * @param int $userId The user to check * @param int $projectId The project to check * @return bool True if the user has a favorite reaction on the project * * @api */ public function userHasFavoritedProject(int $userId, int $projectId): bool { $userReaction = $this->reactionsService->getUserReactions($userId, 'project', $projectId, Reactions::$favorite); return $userReaction && is_array($userReaction); } /** * Builds the base URL used to delete a dashboard comment. * * Derives scheme/host/path from the current request URL and appends the * delComment query parameter, leaving the caller to suffix the comment id. * * @return string The delete-comment URL base (ends with 'delComment=') * * @api */ public function buildDeleteCommentUrlBase(): string { // Current URL up to (but excluding) any existing query string, plus the delComment flag. return strtok(CURRENT_URL, '?').'?delComment='; } }