Merge pull request #2022 from nextcloud/bugfix/1831/comments-api

This commit is contained in:
Julius Härtl
2020-06-15 15:22:03 +02:00
committed by GitHub
4 changed files with 85 additions and 13 deletions

View File

@@ -985,8 +985,8 @@ A list of comments will be provided under the `ocs.data` key. If no or no more c
}, },
"data": [ "data": [
{ {
"id": "175", "id": 175,
"objectId": "12", "objectId": 12,
"message": "This is a comment with a mention to @alice", "message": "This is a comment with a mention to @alice",
"actorId": "admin", "actorId": "admin",
"actorType": "users", "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 ### 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 | | cardId | Integer | The id of the card |
| message | String | The message of the comment, maximum length is limited to 1000 characters | | 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. 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 - 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 #### Request parameters

View File

@@ -55,7 +55,7 @@ class CommentsApiController extends OCSController {
* @NoAdminRequired * @NoAdminRequired
* @throws StatusException * @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); return $this->commentService->create($cardId, $message, $parentId);
} }
@@ -63,7 +63,7 @@ class CommentsApiController extends OCSController {
* @NoAdminRequired * @NoAdminRequired
* @throws StatusException * @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); return $this->commentService->update($cardId, $commentId, $message);
} }
@@ -71,7 +71,7 @@ class CommentsApiController extends OCSController {
* @NoAdminRequired * @NoAdminRequired
* @throws StatusException * @throws StatusException
*/ */
public function delete(string $cardId, string $commentId): DataResponse { public function delete(int $cardId, int $commentId): DataResponse {
return $this->commentService->delete($cardId, $commentId); return $this->commentService->delete($cardId, $commentId);
} }
} }

View File

@@ -88,20 +88,33 @@ class CommentService {
* @param string $replyTo * @param string $replyTo
* @return DataResponse * @return DataResponse
* @throws BadRequestException * @throws BadRequestException
* @throws NotFoundException * @throws NotFoundException|NoPermissionException
*/ */
public function create(string $cardId, string $message, string $replyTo = '0'): DataResponse { public function create(string $cardId, string $message, string $replyTo = '0'): DataResponse {
if (!is_numeric($cardId)) { if (!is_numeric($cardId)) {
throw new BadRequestException('A valid card id must be provided'); throw new BadRequestException('A valid card id must be provided');
} }
$this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_READ); $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 { try {
$comment = $this->commentsManager->create('users', $this->userId, Application::COMMENT_ENTITY_TYPE, $cardId); $comment = $this->commentsManager->create('users', $this->userId, Application::COMMENT_ENTITY_TYPE, $cardId);
$comment->setMessage($message); $comment->setMessage($message);
$comment->setVerb('comment'); $comment->setVerb('comment');
$comment->setParentId($replyTo); $comment->setParentId($replyTo);
$this->commentsManager->save($comment); $this->commentsManager->save($comment);
return new DataResponse($this->formatComment($comment)); return new DataResponse($this->formatComment($comment, true));
} catch (\InvalidArgumentException $e) { } catch (\InvalidArgumentException $e) {
throw new BadRequestException('Invalid input values'); throw new BadRequestException('Invalid input values');
} catch (MessageTooLongException $e) { } catch (MessageTooLongException $e) {
@@ -122,12 +135,19 @@ class CommentService {
$this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_READ); $this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_READ);
try { try {
$comment = $this->commentsManager->get($commentId); $comment = $this->commentsManager->get($commentId);
if ($comment->getObjectType() !== Application::COMMENT_ENTITY_TYPE || $comment->getObjectId() !== $cardId) {
throw new CommentNotFoundException();
}
} catch (CommentNotFoundException $e) { } catch (CommentNotFoundException $e) {
throw new NotFoundException('No comment found.'); throw new NotFoundException('No comment found.');
} }
if ($comment->getActorType() !== 'users' || $comment->getActorId() !== $this->userId) { if ($comment->getActorType() !== 'users' || $comment->getActorId() !== $this->userId) {
throw new NoPermissionException('Only authors are allowed to edit their comment.'); 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); $comment->setMessage($message);
$this->commentsManager->save($comment); $this->commentsManager->save($comment);
return new DataResponse($this->formatComment($comment)); return new DataResponse($this->formatComment($comment));
@@ -141,8 +161,12 @@ class CommentService {
throw new BadRequestException('A valid comment id must be provided'); throw new BadRequestException('A valid comment id must be provided');
} }
$this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_READ); $this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_READ);
try { try {
$comment = $this->commentsManager->get($commentId); $comment = $this->commentsManager->get($commentId);
if ($comment->getObjectType() !== Application::COMMENT_ENTITY_TYPE || $comment->getObjectId() !== $cardId) {
throw new CommentNotFoundException();
}
} catch (CommentNotFoundException $e) { } catch (CommentNotFoundException $e) {
throw new NotFoundException('No comment found.'); throw new NotFoundException('No comment found.');
} }
@@ -153,13 +177,13 @@ class CommentService {
return new DataResponse([]); return new DataResponse([]);
} }
private function formatComment(IComment $comment): array { private function formatComment(IComment $comment, $addReplyTo = false): array {
$user = $this->userManager->get($comment->getActorId()); $user = $this->userManager->get($comment->getActorId());
$actorDisplayName = $user !== null ? $user->getDisplayName() : $comment->getActorId(); $actorDisplayName = $user !== null ? $user->getDisplayName() : $comment->getActorId();
return [ $formattedComment = [
'id' => $comment->getId(), 'id' => (int)$comment->getId(),
'objectId' => $comment->getObjectId(), 'objectId' => (int)$comment->getObjectId(),
'message' => $comment->getMessage(), 'message' => $comment->getMessage(),
'actorId' => $comment->getActorId(), 'actorId' => $comment->getActorId(),
'actorType' => $comment->getActorType(), 'actorType' => $comment->getActorType(),
@@ -181,5 +205,13 @@ class CommentService {
]; ];
}, $comment->getMentions()), }, $comment->getMentions()),
]; ];
try {
if ($addReplyTo && $comment->getParentId() !== '0' && $replyTo = $this->commentsManager->get($comment->getParentId())) {
$formattedComment['replyTo'] = $this->formatComment($replyTo);
}
} catch (CommentNotFoundException $e) {
}
return $formattedComment;
} }
} }

View File

@@ -32,6 +32,7 @@ export class CommentApi {
async loadComments({ cardId, limit, offset }) { async loadComments({ cardId, limit, offset }) {
const api = await axios.get(generateOcsUrl(`apps/deck/api/v1.0/cards`, 2) + `${cardId}/comments`, { const api = await axios.get(generateOcsUrl(`apps/deck/api/v1.0/cards`, 2) + `${cardId}/comments`, {
params: { limit, offset },
headers: { 'OCS-APIRequest': 'true' }, headers: { 'OCS-APIRequest': 'true' },
}) })
return api.data.ocs.data return api.data.ocs.data