live updates for boards
Signed-off-by: chandi Langecker <git@chandi.it>
This commit is contained in:
committed by
Julius Härtl
parent
9674c344ea
commit
322ee92573
@@ -154,6 +154,12 @@ class Application extends App implements IBootstrap {
|
|||||||
// Event listening for realtime updates via notify_push
|
// Event listening for realtime updates via notify_push
|
||||||
$context->registerEventListener(SessionCreatedEvent::class, LiveUpdateListener::class);
|
$context->registerEventListener(SessionCreatedEvent::class, LiveUpdateListener::class);
|
||||||
$context->registerEventListener(SessionClosedEvent::class, LiveUpdateListener::class);
|
$context->registerEventListener(SessionClosedEvent::class, LiveUpdateListener::class);
|
||||||
|
$context->registerEventListener(CardCreatedEvent::class, LiveUpdateListener::class);
|
||||||
|
$context->registerEventListener(CardUpdatedEvent::class, LiveUpdateListener::class);
|
||||||
|
$context->registerEventListener(CardDeletedEvent::class, LiveUpdateListener::class);
|
||||||
|
$context->registerEventListener(AclCreatedEvent::class, LiveUpdateListener::class);
|
||||||
|
$context->registerEventListener(AclUpdatedEvent::class, LiveUpdateListener::class);
|
||||||
|
$context->registerEventListener(AclDeletedEvent::class, LiveUpdateListener::class);
|
||||||
|
|
||||||
$context->registerNotifierService(Notifier::class);
|
$context->registerNotifierService(Notifier::class);
|
||||||
$context->registerEventListener(LoadAdditionalScriptsEvent::class, ResourceAdditionalScriptsListener::class);
|
$context->registerEventListener(LoadAdditionalScriptsEvent::class, ResourceAdditionalScriptsListener::class);
|
||||||
|
|||||||
@@ -29,16 +29,24 @@ use OCP\AppFramework\Db\MultipleObjectsReturnedException;
|
|||||||
use OCP\Cache\CappedMemoryCache;
|
use OCP\Cache\CappedMemoryCache;
|
||||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||||
use OCP\IDBConnection;
|
use OCP\IDBConnection;
|
||||||
|
use OCP\ICache;
|
||||||
|
use OCP\ICacheFactory;
|
||||||
|
|
||||||
/** @template-extends DeckMapper<Stack> */
|
/** @template-extends DeckMapper<Stack> */
|
||||||
class StackMapper extends DeckMapper implements IPermissionMapper {
|
class StackMapper extends DeckMapper implements IPermissionMapper {
|
||||||
private CappedMemoryCache $stackCache;
|
private CappedMemoryCache $stackCache;
|
||||||
private CardMapper $cardMapper;
|
private CardMapper $cardMapper;
|
||||||
|
private ICache $cache;
|
||||||
|
|
||||||
public function __construct(IDBConnection $db, CardMapper $cardMapper) {
|
public function __construct(
|
||||||
|
IDBConnection $db,
|
||||||
|
CardMapper $cardMapper,
|
||||||
|
ICacheFactory $cacheFactory
|
||||||
|
) {
|
||||||
parent::__construct($db, 'deck_stacks', Stack::class);
|
parent::__construct($db, 'deck_stacks', Stack::class);
|
||||||
$this->cardMapper = $cardMapper;
|
$this->cardMapper = $cardMapper;
|
||||||
$this->stackCache = new CappedMemoryCache();
|
$this->stackCache = new CappedMemoryCache();
|
||||||
|
$this->cache = $cacheFactory->createDistributed('deck-stackMapper');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -157,12 +165,19 @@ class StackMapper extends DeckMapper implements IPermissionMapper {
|
|||||||
* @throws \OCP\DB\Exception
|
* @throws \OCP\DB\Exception
|
||||||
*/
|
*/
|
||||||
public function findBoardId($id): ?int {
|
public function findBoardId($id): ?int {
|
||||||
|
$result = $this->cache->get('findBoardId:' . $id);
|
||||||
|
if ($result !== null) {
|
||||||
|
return $result !== false ? $result : null;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
$entity = $this->find($id);
|
$entity = $this->find($id);
|
||||||
return $entity->getBoardId();
|
$result = $entity->getBoardId();
|
||||||
} catch (DoesNotExistException $e) {
|
} catch (DoesNotExistException $e) {
|
||||||
|
$result = false;
|
||||||
} catch (MultipleObjectsReturnedException $e) {
|
} catch (MultipleObjectsReturnedException $e) {
|
||||||
}
|
}
|
||||||
return null;
|
$this->cache->set('findBoardId:' . $id, $result);
|
||||||
|
|
||||||
|
return $result !== false ? $result : null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,5 +26,17 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace OCA\Deck\Event;
|
namespace OCA\Deck\Event;
|
||||||
|
|
||||||
|
use OCA\Deck\Db\Card;
|
||||||
|
|
||||||
class CardUpdatedEvent extends ACardEvent {
|
class CardUpdatedEvent extends ACardEvent {
|
||||||
|
private $cardBefore;
|
||||||
|
|
||||||
|
public function __construct(Card $card, Card $before = null) {
|
||||||
|
parent::__construct($card);
|
||||||
|
$this->cardBefore = $before;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCardBefore() {
|
||||||
|
return $this->cardBefore;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,11 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace OCA\Deck\Listeners;
|
namespace OCA\Deck\Listeners;
|
||||||
|
|
||||||
|
use OCA\Deck\Db\StackMapper;
|
||||||
use OCA\Deck\NotifyPushEvents;
|
use OCA\Deck\NotifyPushEvents;
|
||||||
|
use OCA\Deck\Event\AAclEvent;
|
||||||
|
use OCA\Deck\Event\ACardEvent;
|
||||||
|
use OCA\Deck\Event\CardUpdatedEvent;
|
||||||
use OCA\Deck\Event\SessionClosedEvent;
|
use OCA\Deck\Event\SessionClosedEvent;
|
||||||
use OCA\Deck\Event\SessionCreatedEvent;
|
use OCA\Deck\Event\SessionCreatedEvent;
|
||||||
use OCA\Deck\Service\SessionService;
|
use OCA\Deck\Service\SessionService;
|
||||||
@@ -42,13 +46,15 @@ class LiveUpdateListener implements IEventListener {
|
|||||||
private LoggerInterface $logger;
|
private LoggerInterface $logger;
|
||||||
private SessionService $sessionService;
|
private SessionService $sessionService;
|
||||||
private IRequest $request;
|
private IRequest $request;
|
||||||
|
private StackMapper $stackMapper;
|
||||||
private $queue;
|
private $queue;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
ContainerInterface $container,
|
ContainerInterface $container,
|
||||||
IRequest $request,
|
IRequest $request,
|
||||||
LoggerInterface $logger,
|
LoggerInterface $logger,
|
||||||
SessionService $sessionService
|
SessionService $sessionService,
|
||||||
|
StackMapper $stackMapper
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
$this->queue = $container->get(IQueue::class);
|
$this->queue = $container->get(IQueue::class);
|
||||||
@@ -59,6 +65,7 @@ class LiveUpdateListener implements IEventListener {
|
|||||||
$this->logger = $logger;
|
$this->logger = $logger;
|
||||||
$this->sessionService = $sessionService;
|
$this->sessionService = $sessionService;
|
||||||
$this->request = $request;
|
$this->request = $request;
|
||||||
|
$this->stackMapper = $stackMapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function handle(Event $event): void {
|
public function handle(Event $event): void {
|
||||||
@@ -68,17 +75,36 @@ class LiveUpdateListener implements IEventListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// the web frontend is adding the Session-ID as a header on every request
|
// the web frontend is adding the Session-ID as a header
|
||||||
// TODO: verify the token! this currently allows to spoof a token from someone
|
// TODO: verify the token! this currently allows to spoof a token from someone
|
||||||
// else, preventing this person from getting any live updates
|
// else, preventing this person from getting updates
|
||||||
$causingSessionToken = $this->request->getHeader('x-nc-deck-session');
|
$causingSessionToken = $this->request->getHeader('x-nc-deck-session');
|
||||||
if (
|
if (
|
||||||
$event instanceof SessionCreatedEvent ||
|
$event instanceof SessionCreatedEvent ||
|
||||||
$event instanceof SessionClosedEvent
|
$event instanceof SessionClosedEvent ||
|
||||||
|
$event instanceof AAclEvent
|
||||||
) {
|
) {
|
||||||
$this->sessionService->notifyAllSessions($this->queue, $event->getBoardId(), NotifyPushEvents::DeckBoardUpdate, [
|
$this->sessionService->notifyAllSessions($this->queue, $event->getBoardId(), NotifyPushEvents::DeckBoardUpdate, [
|
||||||
'id' => $event->getBoardId()
|
'id' => $event->getBoardId()
|
||||||
], $causingSessionToken);
|
], $causingSessionToken);
|
||||||
|
} elseif ($event instanceof ACardEvent) {
|
||||||
|
$boardId = $this->stackMapper->findBoardId($event->getCard()->getStackId());
|
||||||
|
$this->sessionService->notifyAllSessions($this->queue, $boardId, NotifyPushEvents::DeckCardUpdate, [
|
||||||
|
'boardId' => $boardId,
|
||||||
|
'cardId' => $event->getCard()->getId()
|
||||||
|
], $causingSessionToken);
|
||||||
|
|
||||||
|
// if card got moved to a diferent board, we should notify
|
||||||
|
// also sessions active on the previous board
|
||||||
|
if ($event instanceof CardUpdatedEvent && $event->getCardBefore()) {
|
||||||
|
$previousBoardId = $this->stackMapper->findBoardId($event->getCardBefore()->getStackId());
|
||||||
|
if ($boardId !== $previousBoardId) {
|
||||||
|
$this->sessionService->notifyAllSessions($this->queue, $previousBoardId, NotifyPushEvents::DeckCardUpdate, [
|
||||||
|
'boardId' => $boardId,
|
||||||
|
'cardId' => $event->getCard()->getId()
|
||||||
|
], $causingSessionToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$this->logger->error('Error when handling live update event', ['exception' => $e]);
|
$this->logger->error('Error when handling live update event', ['exception' => $e]);
|
||||||
|
|||||||
@@ -26,4 +26,5 @@ namespace OCA\Deck;
|
|||||||
|
|
||||||
class NotifyPushEvents {
|
class NotifyPushEvents {
|
||||||
public const DeckBoardUpdate = 'deck_board_update';
|
public const DeckBoardUpdate = 'deck_board_update';
|
||||||
|
public const DeckCardUpdate = 'deck_card_update';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -353,7 +353,7 @@ class CardService {
|
|||||||
}
|
}
|
||||||
$this->changeHelper->cardChanged($card->getId(), true);
|
$this->changeHelper->cardChanged($card->getId(), true);
|
||||||
|
|
||||||
$this->eventDispatcher->dispatchTyped(new CardUpdatedEvent($card));
|
$this->eventDispatcher->dispatchTyped(new CardUpdatedEvent($card, $changes->getBefore()));
|
||||||
|
|
||||||
return $card;
|
return $card;
|
||||||
}
|
}
|
||||||
@@ -443,6 +443,8 @@ class CardService {
|
|||||||
$result[$card->getOrder()] = $card;
|
$result[$card->getOrder()] = $card;
|
||||||
}
|
}
|
||||||
$this->changeHelper->cardChanged($id, false);
|
$this->changeHelper->cardChanged($id, false);
|
||||||
|
$this->eventDispatcher->dispatchTyped(new CardUpdatedEvent($card));
|
||||||
|
|
||||||
return array_values($result);
|
return array_values($result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -52,6 +52,18 @@ hasPush = listen('deck_board_update', (name, body) => {
|
|||||||
store.dispatch('refreshBoard', currentBoardId)
|
store.dispatch('refreshBoard', currentBoardId)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
listen('deck_card_update', (name, body) => {
|
||||||
|
|
||||||
|
// ignore update events which we have triggered ourselves
|
||||||
|
if (isOurSessionToken(body._causingSessionToken)) return
|
||||||
|
|
||||||
|
// only handle update events for the currently open board
|
||||||
|
const currentBoardId = store.state.currentBoard?.id
|
||||||
|
if (body.boardId !== currentBoardId) return
|
||||||
|
|
||||||
|
store.dispatch('loadStacks', currentBoardId)
|
||||||
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* is the notify_push app active and can
|
* is the notify_push app active and can
|
||||||
* provide us with real time updates?
|
* provide us with real time updates?
|
||||||
|
|||||||
@@ -333,10 +333,15 @@ export default new Vuex.Store({
|
|||||||
commit('setAssignableUsers', board.users)
|
commit('setAssignableUsers', board.users)
|
||||||
},
|
},
|
||||||
|
|
||||||
async refreshBoard({ commit }, boardId) {
|
async refreshBoard({ commit, dispatch }, boardId) {
|
||||||
const board = await apiClient.loadById(boardId)
|
const board = await apiClient.loadById(boardId)
|
||||||
|
const etagHasChanged = board.ETag !== this.state.currentBoard.ETag
|
||||||
commit('setCurrentBoard', board)
|
commit('setCurrentBoard', board)
|
||||||
commit('setAssignableUsers', board.users)
|
commit('setAssignableUsers', board.users)
|
||||||
|
|
||||||
|
if (etagHasChanged) {
|
||||||
|
dispatch('loadStacks', boardId)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleShowArchived({ commit }) {
|
toggleShowArchived({ commit }) {
|
||||||
|
|||||||
Reference in New Issue
Block a user