Merge pull request #2245 from nextcloud/enh/etag-api
This commit is contained in:
25
docs/API.md
25
docs/API.md
@@ -69,6 +69,31 @@ curl -u admin:admin -X GET \
|
||||
-H "If-Modified-Since: Mon, 05 Nov 2018 09:28:00 GMT"
|
||||
```
|
||||
|
||||
### ETag
|
||||
|
||||
An ETag header is returned in order to determine if further child elements have been updated for the following endpoints:
|
||||
|
||||
- Fetch all user board `GET /api/v1.0/boards`
|
||||
- Fetch a single board `GET /api/v1.0/boards/{boardId}`
|
||||
- Fetch all stacks of a board `GET /api/v1.0/boards/{boardId}/stacks`
|
||||
- Fetch a single stacks of a board `GET /api/v1.0/boards/{boardId}/stacks/{stackId}`
|
||||
- Fetch a single card of a board `GET /api/v1.0/boards/{boardId}/stacks/{stackId}/cards/{cardId}`
|
||||
- Fetch attachments of a card `GET /api/v1.0/boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments`
|
||||
|
||||
If a `If-None-Match` header is provided and the requested element has not changed a `304` Not Modified response will be returned.
|
||||
|
||||
Changes of child elements will propagate to their parents and also cause an update of the ETag which will be useful for determining if a sync is necessary on any client integration side. As an example, if a label is added to a card, the ETag of all related entities (the card, stack and board) will change.
|
||||
|
||||
If available the ETag will also be part of JSON response objects as shown below for a card:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 81,
|
||||
"ETag": "bdb10fa2d2aeda092a2b6b469454dc90",
|
||||
"title": "Test card"
|
||||
}
|
||||
```
|
||||
|
||||
# Changelog
|
||||
|
||||
## 1.0.0 (unreleased)
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
namespace OCA\Deck\Activity;
|
||||
|
||||
use OCA\Deck\Db\CardMapper;
|
||||
use OCA\Deck\Db\ChangeHelper;
|
||||
use OCA\Deck\Notification\NotificationHelper;
|
||||
use OCP\Comments\CommentsEvent;
|
||||
use OCP\Comments\IComment;
|
||||
@@ -40,10 +41,14 @@ class CommentEventHandler implements ICommentsEventHandler {
|
||||
/** @var CardMapper */
|
||||
private $cardMapper;
|
||||
|
||||
public function __construct(ActivityManager $activityManager, NotificationHelper $notificationHelper, CardMapper $cardMapper) {
|
||||
/** @var ChangeHelper */
|
||||
private $changeHelper;
|
||||
|
||||
public function __construct(ActivityManager $activityManager, NotificationHelper $notificationHelper, CardMapper $cardMapper, ChangeHelper $changeHelper) {
|
||||
$this->notificationHelper = $notificationHelper;
|
||||
$this->activityManager = $activityManager;
|
||||
$this->cardMapper = $cardMapper;
|
||||
$this->changeHelper = $changeHelper;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -54,6 +59,8 @@ class CommentEventHandler implements ICommentsEventHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->changeHelper->cardChanged($event->getComment()->getObjectId());
|
||||
|
||||
$eventType = $event->getEvent();
|
||||
if ($eventType === CommentsEvent::EVENT_ADD
|
||||
) {
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
namespace OCA\Deck\Controller;
|
||||
|
||||
use OCA\Deck\Db\Board;
|
||||
use OCA\Deck\StatusException;
|
||||
use OCP\AppFramework\ApiController;
|
||||
use OCP\AppFramework\Http;
|
||||
@@ -72,7 +73,11 @@ class BoardApiController extends ApiController {
|
||||
}
|
||||
$boards = $this->boardService->findAll($date->getTimestamp(), $details);
|
||||
}
|
||||
return new DataResponse($boards, HTTP::STATUS_OK);
|
||||
$response = new DataResponse($boards, HTTP::STATUS_OK);
|
||||
$response->setETag(md5(json_encode(array_map(function (Board $board) {
|
||||
return $board->getId() . '-' . $board->getETag();
|
||||
}, $boards))));
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -85,7 +90,9 @@ class BoardApiController extends ApiController {
|
||||
*/
|
||||
public function get() {
|
||||
$board = $this->boardService->find($this->request->getParam('boardId'));
|
||||
return new DataResponse($board, HTTP::STATUS_OK);
|
||||
$response = new DataResponse($board, HTTP::STATUS_OK);
|
||||
$response->setETag($board->getEtag());
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -64,7 +64,9 @@ class CardApiController extends ApiController {
|
||||
*/
|
||||
public function get() {
|
||||
$card = $this->cardService->find($this->request->getParam('cardId'));
|
||||
return new DataResponse($card, HTTP::STATUS_OK);
|
||||
$response = new DataResponse($card, HTTP::STATUS_OK);
|
||||
$response->setETag($card->getEtag());
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -83,7 +83,9 @@ class StackApiController extends ApiController {
|
||||
*/
|
||||
public function get() {
|
||||
$stack = $this->stackService->find($this->request->getParam('stackId'));
|
||||
return new DataResponse($stack, HTTP::STATUS_OK);
|
||||
$response = new DataResponse($stack, HTTP::STATUS_OK);
|
||||
$response->setETag($stack->getETag());
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -81,4 +81,8 @@ class Board extends RelationalEntity {
|
||||
$this->acl[] = $a;
|
||||
}
|
||||
}
|
||||
|
||||
public function getETag() {
|
||||
return md5((string)$this->getLastModified());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,4 +155,8 @@ class Card extends RelationalEntity {
|
||||
public function getCalendarPrefix(): string {
|
||||
return 'card';
|
||||
}
|
||||
|
||||
public function getETag() {
|
||||
return md5((string)$this->getLastModified());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,4 +36,8 @@ class Label extends RelationalEntity {
|
||||
$this->addType('cardId', 'integer');
|
||||
$this->addType('lastModified', 'integer');
|
||||
}
|
||||
|
||||
public function getETag() {
|
||||
return md5((string)$this->getLastModified());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,6 +80,9 @@ class RelationalEntity extends Entity implements \JsonSerializable {
|
||||
$json[$property] = $value;
|
||||
}
|
||||
}
|
||||
if ($reflection->hasMethod('getETag')) {
|
||||
$json['ETag'] = $this->getETag();
|
||||
}
|
||||
return $json;
|
||||
}
|
||||
|
||||
|
||||
@@ -65,4 +65,8 @@ class Stack extends RelationalEntity {
|
||||
public function getCalendarPrefix(): string {
|
||||
return 'stack';
|
||||
}
|
||||
|
||||
public function getETag() {
|
||||
return md5((string)$this->getLastModified());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,7 +148,7 @@ class AssignmentService {
|
||||
$assignment->setParticipant($userId);
|
||||
$assignment->setType($type);
|
||||
$assignment = $this->assignedUsersMapper->insert($assignment);
|
||||
$this->changeHelper->cardChanged($cardId, false);
|
||||
$this->changeHelper->cardChanged($cardId);
|
||||
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $card, ActivityManager::SUBJECT_CARD_USER_ASSIGN, ['assigneduser' => $userId]);
|
||||
|
||||
$this->eventDispatcher->dispatch(
|
||||
@@ -185,7 +185,7 @@ class AssignmentService {
|
||||
$assignment = $this->assignedUsersMapper->delete($assignment);
|
||||
$card = $this->cardMapper->find($cardId);
|
||||
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $card, ActivityManager::SUBJECT_CARD_USER_UNASSIGN, ['assigneduser' => $userId]);
|
||||
$this->changeHelper->cardChanged($cardId, false);
|
||||
$this->changeHelper->cardChanged($cardId);
|
||||
|
||||
$this->eventDispatcher->dispatch(
|
||||
'\OCA\Deck\Card::onUpdate', new FTSEvent(null, ['id' => $cardId, 'card' => $card])
|
||||
|
||||
@@ -320,6 +320,7 @@ class AttachmentService {
|
||||
if ($service->allowUndo()) {
|
||||
$service->markAsDeleted($attachment);
|
||||
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $attachment, ActivityManager::SUBJECT_ATTACHMENT_DELETE);
|
||||
$this->changeHelper->cardChanged($attachment->getCardId());
|
||||
return $this->attachmentMapper->update($attachment);
|
||||
}
|
||||
$service->delete($attachment);
|
||||
|
||||
@@ -538,7 +538,7 @@ class CardService {
|
||||
}
|
||||
$label = $this->labelMapper->find($labelId);
|
||||
$this->cardMapper->assignLabel($cardId, $labelId);
|
||||
$this->changeHelper->cardChanged($cardId, false);
|
||||
$this->changeHelper->cardChanged($cardId);
|
||||
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $card, ActivityManager::SUBJECT_LABEL_ASSIGN, ['label' => $label]);
|
||||
|
||||
$this->eventDispatcher->dispatch(
|
||||
@@ -574,7 +574,7 @@ class CardService {
|
||||
}
|
||||
$label = $this->labelMapper->find($labelId);
|
||||
$this->cardMapper->removeLabel($cardId, $labelId);
|
||||
$this->changeHelper->cardChanged($cardId, false);
|
||||
$this->changeHelper->cardChanged($cardId);
|
||||
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $card, ActivityManager::SUBJECT_LABEL_UNASSING, ['label' => $label]);
|
||||
|
||||
$this->eventDispatcher->dispatch(
|
||||
|
||||
@@ -25,6 +25,7 @@ namespace OCA\Deck\Activity;
|
||||
|
||||
use OCA\Deck\Db\Card;
|
||||
use OCA\Deck\Db\CardMapper;
|
||||
use OCA\Deck\Db\ChangeHelper;
|
||||
use OCA\Deck\Notification\NotificationHelper;
|
||||
use OCP\Comments\CommentsEvent;
|
||||
use OCP\Comments\IComment;
|
||||
@@ -45,10 +46,12 @@ class CommentEventHandlerTest extends TestCase {
|
||||
$this->activityManager = $this->createMock(ActivityManager::class);
|
||||
$this->notificationHelper = $this->createMock(NotificationHelper::class);
|
||||
$this->cardMapper = $this->createMock(CardMapper::class);
|
||||
$this->changeHelper = $this->createMock(ChangeHelper::class);
|
||||
$this->commentEventHandler = new CommentEventHandler(
|
||||
$this->activityManager,
|
||||
$this->notificationHelper,
|
||||
$this->cardMapper
|
||||
$this->cardMapper,
|
||||
$this->changeHelper
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ class BoardTest extends TestCase {
|
||||
'archived' => false,
|
||||
'users' => ['user1', 'user2'],
|
||||
'settings' => [],
|
||||
'ETag' => $board->getETag(),
|
||||
], $board->jsonSerialize());
|
||||
}
|
||||
|
||||
@@ -52,6 +53,7 @@ class BoardTest extends TestCase {
|
||||
'archived' => false,
|
||||
'users' => [],
|
||||
'settings' => [],
|
||||
'ETag' => $board->getETag(),
|
||||
], $board->jsonSerialize());
|
||||
}
|
||||
public function testSetAcl() {
|
||||
@@ -80,6 +82,7 @@ class BoardTest extends TestCase {
|
||||
'shared' => 1,
|
||||
'users' => [],
|
||||
'settings' => [],
|
||||
'ETag' => $board->getETag(),
|
||||
], $board->jsonSerialize());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,6 +84,7 @@ class CardTest extends TestCase {
|
||||
'deletedAt' => 0,
|
||||
'commentsUnread' => 0,
|
||||
'lastEditor' => null,
|
||||
'ETag' => $card->getETag(),
|
||||
], $card->jsonSerialize());
|
||||
}
|
||||
public function testJsonSerializeLabels() {
|
||||
@@ -109,6 +110,7 @@ class CardTest extends TestCase {
|
||||
'deletedAt' => 0,
|
||||
'commentsUnread' => 0,
|
||||
'lastEditor' => null,
|
||||
'ETag' => $card->getETag(),
|
||||
], $card->jsonSerialize());
|
||||
}
|
||||
|
||||
@@ -144,6 +146,7 @@ class CardTest extends TestCase {
|
||||
'deletedAt' => 0,
|
||||
'commentsUnread' => 0,
|
||||
'lastEditor' => null,
|
||||
'ETag' => $card->getETag(),
|
||||
], $card->jsonSerialize());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,8 @@ class LabelTest extends TestCase {
|
||||
'boardId' => 123,
|
||||
'cardId' => null,
|
||||
'lastModified' => null,
|
||||
'color' => '000000'
|
||||
'color' => '000000',
|
||||
'ETag' => $label->getETag(),
|
||||
], $label->jsonSerialize());
|
||||
}
|
||||
public function testJsonSerializeCard() {
|
||||
@@ -54,7 +55,8 @@ class LabelTest extends TestCase {
|
||||
'boardId' => null,
|
||||
'cardId' => 123,
|
||||
'lastModified' => null,
|
||||
'color' => '000000'
|
||||
'color' => '000000',
|
||||
'ETag' => $label->getETag(),
|
||||
], $label->jsonSerialize());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ class StackTest extends \Test\TestCase {
|
||||
return $board;
|
||||
}
|
||||
public function testJsonSerialize() {
|
||||
$board = $this->createStack();
|
||||
$stack = $this->createStack();
|
||||
$this->assertEquals([
|
||||
'id' => 1,
|
||||
'title' => "My Stack",
|
||||
@@ -41,12 +41,13 @@ class StackTest extends \Test\TestCase {
|
||||
'boardId' => 1,
|
||||
'deletedAt' => 0,
|
||||
'lastModified' => 0,
|
||||
], $board->jsonSerialize());
|
||||
'ETag' => $stack->getETag(),
|
||||
], $stack->jsonSerialize());
|
||||
}
|
||||
public function testJsonSerializeWithCards() {
|
||||
$cards = ["foo", "bar"];
|
||||
$board = $this->createStack();
|
||||
$board->setCards($cards);
|
||||
$stack = $this->createStack();
|
||||
$stack->setCards($cards);
|
||||
$this->assertEquals([
|
||||
'id' => 1,
|
||||
'title' => "My Stack",
|
||||
@@ -55,6 +56,7 @@ class StackTest extends \Test\TestCase {
|
||||
'cards' => ["foo", "bar"],
|
||||
'deletedAt' => 0,
|
||||
'lastModified' => 0,
|
||||
], $board->jsonSerialize());
|
||||
'ETag' => $stack->getETag(),
|
||||
], $stack->jsonSerialize());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ class BoardApiControllerTest extends \Test\TestCase {
|
||||
|
||||
$expected = new DataResponse($boards, HTTP::STATUS_OK);
|
||||
$actual = $this->controller->index();
|
||||
|
||||
$actual->setETag(null);
|
||||
$this->assertEquals($expected, $actual);
|
||||
}
|
||||
|
||||
@@ -90,6 +90,7 @@ class BoardApiControllerTest extends \Test\TestCase {
|
||||
->will($this->returnValue($boardId));
|
||||
|
||||
$expected = new DataResponse($board, HTTP::STATUS_OK);
|
||||
$expected->setETag($board->getETag());
|
||||
$actual = $this->controller->get();
|
||||
$this->assertEquals($expected, $actual);
|
||||
}
|
||||
|
||||
@@ -72,6 +72,7 @@ class CardApiControllerTest extends \Test\TestCase {
|
||||
->willReturn($card);
|
||||
|
||||
$expected = new DataResponse($card, HTTP::STATUS_OK);
|
||||
$expected->setETag($card->getETag());
|
||||
$actual = $this->controller->get();
|
||||
$this->assertEquals($expected, $actual);
|
||||
}
|
||||
|
||||
@@ -78,6 +78,7 @@ class StackApiControllerTest extends \Test\TestCase {
|
||||
|
||||
$expected = new DataResponse($stacks, HTTP::STATUS_OK);
|
||||
$actual = $this->controller->index();
|
||||
$actual->setETag(null);
|
||||
$this->assertEquals($expected, $actual);
|
||||
}
|
||||
|
||||
@@ -97,6 +98,7 @@ class StackApiControllerTest extends \Test\TestCase {
|
||||
->willReturn($this->exampleStack['id']);
|
||||
|
||||
$expected = new DataResponse($stack, HTTP::STATUS_OK);
|
||||
$expected->setETag($stack->getETag());
|
||||
$actual = $this->controller->get();
|
||||
$this->assertEquals($expected, $actual);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user