diff --git a/docs/API.md b/docs/API.md index 43f4720d4..e764b3cbc 100644 --- a/docs/API.md +++ b/docs/API.md @@ -985,8 +985,8 @@ A list of comments will be provided under the `ocs.data` key. If no or no more c }, "data": [ { - "id": "175", - "objectId": "12", + "id": 175, + "objectId": 12, "message": "This is a comment with a mention to @alice", "actorId": "admin", "actorType": "users", @@ -1005,6 +1005,45 @@ A list of comments will be provided under the `ocs.data` key. If no or no more c } ``` +In case a comment is marked as a reply to another comment object, the parent comment will be added as `replyTo` entry to the response. Only the next parent node is added, nested replies are not exposed directly. + +```json +[ + { + "id": 175, + "objectId": 12, + "message": "This is a comment with a mention to @alice", + "actorId": "admin", + "actorType": "users", + "actorDisplayName": "Administrator", + "creationDateTime": "2020-03-10T10:23:07+00:00", + "mentions": [ + { + "mentionId": "alice", + "mentionType": "user", + "mentionDisplayName": "alice" + } + ], + "replyTo": { + "id": 175, + "objectId": 12, + "message": "This is a comment with a mention to @alice", + "actorId": "admin", + "actorType": "users", + "actorDisplayName": "Administrator", + "creationDateTime": "2020-03-10T10:23:07+00:00", + "mentions": [ + { + "mentionId": "alice", + "mentionType": "user", + "mentionDisplayName": "alice" + } + ] + } + } +] +``` + ### POST /cards/{cardId}/comments - Create a new comment @@ -1014,7 +1053,7 @@ A list of comments will be provided under the `ocs.data` key. If no or no more c | --------- | ------- | --------------------------------------- | | cardId | Integer | The id of the card | | message | String | The message of the comment, maximum length is limited to 1000 characters | -| parentId | Integer | The start offset used for pagination, defaults to null | +| parentId | Integer | _(optional)_ The start offset used for pagination, defaults to null | Mentions will be parsed by the server. The server will return a list of mentions in the response to this request as shown below. @@ -1070,7 +1109,7 @@ A not found response might be returned if: - The parent comment could not be found -### PUT /cards/{cardId}/comments/{commentId} - Update a new comment +### PUT /cards/{cardId}/comments/{commentId} - Update a comment #### Request parameters diff --git a/lib/Controller/CommentsApiController.php b/lib/Controller/CommentsApiController.php index 3fc410d75..ca03bcacc 100644 --- a/lib/Controller/CommentsApiController.php +++ b/lib/Controller/CommentsApiController.php @@ -55,7 +55,7 @@ class CommentsApiController extends OCSController { * @NoAdminRequired * @throws StatusException */ - public function create(string $cardId, string $message, string $parentId = '0'): DataResponse { + public function create(int $cardId, string $message, int $parentId = 0): DataResponse { return $this->commentService->create($cardId, $message, $parentId); } @@ -63,7 +63,7 @@ class CommentsApiController extends OCSController { * @NoAdminRequired * @throws StatusException */ - public function update(string $cardId, string $commentId, string $message): DataResponse { + public function update(int $cardId, int $commentId, string $message): DataResponse { return $this->commentService->update($cardId, $commentId, $message); } @@ -71,7 +71,7 @@ class CommentsApiController extends OCSController { * @NoAdminRequired * @throws StatusException */ - public function delete(string $cardId, string $commentId): DataResponse { + public function delete(int $cardId, int $commentId): DataResponse { return $this->commentService->delete($cardId, $commentId); } } diff --git a/lib/Service/CommentService.php b/lib/Service/CommentService.php index d805dfc62..c389a9ad2 100644 --- a/lib/Service/CommentService.php +++ b/lib/Service/CommentService.php @@ -88,20 +88,33 @@ class CommentService { * @param string $replyTo * @return DataResponse * @throws BadRequestException - * @throws NotFoundException + * @throws NotFoundException|NoPermissionException */ public function create(string $cardId, string $message, string $replyTo = '0'): DataResponse { if (!is_numeric($cardId)) { throw new BadRequestException('A valid card id must be provided'); } $this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_READ); + + // Check if parent is a comment on the same card + if ($replyTo !== '0') { + try { + $comment = $this->commentsManager->get($replyTo); + if ($comment->getObjectType() !== Application::COMMENT_ENTITY_TYPE || $comment->getObjectId() !== $cardId) { + throw new CommentNotFoundException(); + } + } catch (CommentNotFoundException $e) { + throw new BadRequestException('Invalid parent id: The parent comment was not found or belongs to a different card'); + } + } + try { $comment = $this->commentsManager->create('users', $this->userId, Application::COMMENT_ENTITY_TYPE, $cardId); $comment->setMessage($message); $comment->setVerb('comment'); $comment->setParentId($replyTo); $this->commentsManager->save($comment); - return new DataResponse($this->formatComment($comment)); + return new DataResponse($this->formatComment($comment, true)); } catch (\InvalidArgumentException $e) { throw new BadRequestException('Invalid input values'); } catch (MessageTooLongException $e) { @@ -122,12 +135,19 @@ class CommentService { $this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_READ); try { $comment = $this->commentsManager->get($commentId); + if ($comment->getObjectType() !== Application::COMMENT_ENTITY_TYPE || $comment->getObjectId() !== $cardId) { + throw new CommentNotFoundException(); + } } catch (CommentNotFoundException $e) { throw new NotFoundException('No comment found.'); } if ($comment->getActorType() !== 'users' || $comment->getActorId() !== $this->userId) { throw new NoPermissionException('Only authors are allowed to edit their comment.'); } + if ($comment->getParentId() !== '0') { + $this->permissionService->checkPermission($this->cardMapper, $comment->getParentId(), Acl::PERMISSION_READ); + } + $comment->setMessage($message); $this->commentsManager->save($comment); return new DataResponse($this->formatComment($comment)); @@ -141,8 +161,12 @@ class CommentService { throw new BadRequestException('A valid comment id must be provided'); } $this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_READ); + try { $comment = $this->commentsManager->get($commentId); + if ($comment->getObjectType() !== Application::COMMENT_ENTITY_TYPE || $comment->getObjectId() !== $cardId) { + throw new CommentNotFoundException(); + } } catch (CommentNotFoundException $e) { throw new NotFoundException('No comment found.'); } @@ -153,13 +177,13 @@ class CommentService { return new DataResponse([]); } - private function formatComment(IComment $comment): array { + private function formatComment(IComment $comment, $addReplyTo = false): array { $user = $this->userManager->get($comment->getActorId()); $actorDisplayName = $user !== null ? $user->getDisplayName() : $comment->getActorId(); - return [ - 'id' => $comment->getId(), - 'objectId' => $comment->getObjectId(), + $formattedComment = [ + 'id' => (int)$comment->getId(), + 'objectId' => (int)$comment->getObjectId(), 'message' => $comment->getMessage(), 'actorId' => $comment->getActorId(), 'actorType' => $comment->getActorType(), @@ -181,5 +205,13 @@ class CommentService { ]; }, $comment->getMentions()), ]; + + try { + if ($addReplyTo && $comment->getParentId() !== '0' && $replyTo = $this->commentsManager->get($comment->getParentId())) { + $formattedComment['replyTo'] = $this->formatComment($replyTo); + } + } catch (CommentNotFoundException $e) { + } + return $formattedComment; } } diff --git a/src/services/CommentApi.js b/src/services/CommentApi.js index 7e2306804..31fd98bef 100644 --- a/src/services/CommentApi.js +++ b/src/services/CommentApi.js @@ -32,6 +32,7 @@ export class CommentApi { async loadComments({ cardId, limit, offset }) { const api = await axios.get(generateOcsUrl(`apps/deck/api/v1.0/cards`, 2) + `${cardId}/comments`, { + params: { limit, offset }, headers: { 'OCS-APIRequest': 'true' }, }) return api.data.ocs.data