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
|
||||
$context->registerEventListener(SessionCreatedEvent::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->registerEventListener(LoadAdditionalScriptsEvent::class, ResourceAdditionalScriptsListener::class);
|
||||
|
||||
@@ -29,16 +29,24 @@ use OCP\AppFramework\Db\MultipleObjectsReturnedException;
|
||||
use OCP\Cache\CappedMemoryCache;
|
||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\ICache;
|
||||
use OCP\ICacheFactory;
|
||||
|
||||
/** @template-extends DeckMapper<Stack> */
|
||||
class StackMapper extends DeckMapper implements IPermissionMapper {
|
||||
private CappedMemoryCache $stackCache;
|
||||
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);
|
||||
$this->cardMapper = $cardMapper;
|
||||
$this->stackCache = new CappedMemoryCache();
|
||||
$this->cache = $cacheFactory->createDistributed('deck-stackMapper');
|
||||
}
|
||||
|
||||
|
||||
@@ -157,12 +165,19 @@ class StackMapper extends DeckMapper implements IPermissionMapper {
|
||||
* @throws \OCP\DB\Exception
|
||||
*/
|
||||
public function findBoardId($id): ?int {
|
||||
$result = $this->cache->get('findBoardId:' . $id);
|
||||
if ($result !== null) {
|
||||
return $result !== false ? $result : null;
|
||||
}
|
||||
try {
|
||||
$entity = $this->find($id);
|
||||
return $entity->getBoardId();
|
||||
$result = $entity->getBoardId();
|
||||
} catch (DoesNotExistException $e) {
|
||||
$result = false;
|
||||
} 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;
|
||||
|
||||
use OCA\Deck\Db\Card;
|
||||
|
||||
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;
|
||||
|
||||
use OCA\Deck\Db\StackMapper;
|
||||
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\SessionCreatedEvent;
|
||||
use OCA\Deck\Service\SessionService;
|
||||
@@ -42,13 +46,15 @@ class LiveUpdateListener implements IEventListener {
|
||||
private LoggerInterface $logger;
|
||||
private SessionService $sessionService;
|
||||
private IRequest $request;
|
||||
private StackMapper $stackMapper;
|
||||
private $queue;
|
||||
|
||||
public function __construct(
|
||||
ContainerInterface $container,
|
||||
IRequest $request,
|
||||
LoggerInterface $logger,
|
||||
SessionService $sessionService
|
||||
SessionService $sessionService,
|
||||
StackMapper $stackMapper
|
||||
) {
|
||||
try {
|
||||
$this->queue = $container->get(IQueue::class);
|
||||
@@ -59,6 +65,7 @@ class LiveUpdateListener implements IEventListener {
|
||||
$this->logger = $logger;
|
||||
$this->sessionService = $sessionService;
|
||||
$this->request = $request;
|
||||
$this->stackMapper = $stackMapper;
|
||||
}
|
||||
|
||||
public function handle(Event $event): void {
|
||||
@@ -68,17 +75,36 @@ class LiveUpdateListener implements IEventListener {
|
||||
}
|
||||
|
||||
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
|
||||
// else, preventing this person from getting any live updates
|
||||
// else, preventing this person from getting updates
|
||||
$causingSessionToken = $this->request->getHeader('x-nc-deck-session');
|
||||
if (
|
||||
$event instanceof SessionCreatedEvent ||
|
||||
$event instanceof SessionClosedEvent
|
||||
$event instanceof SessionClosedEvent ||
|
||||
$event instanceof AAclEvent
|
||||
) {
|
||||
$this->sessionService->notifyAllSessions($this->queue, $event->getBoardId(), NotifyPushEvents::DeckBoardUpdate, [
|
||||
'id' => $event->getBoardId()
|
||||
], $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) {
|
||||
$this->logger->error('Error when handling live update event', ['exception' => $e]);
|
||||
|
||||
@@ -26,4 +26,5 @@ namespace OCA\Deck;
|
||||
|
||||
class NotifyPushEvents {
|
||||
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->eventDispatcher->dispatchTyped(new CardUpdatedEvent($card));
|
||||
$this->eventDispatcher->dispatchTyped(new CardUpdatedEvent($card, $changes->getBefore()));
|
||||
|
||||
return $card;
|
||||
}
|
||||
@@ -443,6 +443,8 @@ class CardService {
|
||||
$result[$card->getOrder()] = $card;
|
||||
}
|
||||
$this->changeHelper->cardChanged($id, false);
|
||||
$this->eventDispatcher->dispatchTyped(new CardUpdatedEvent($card));
|
||||
|
||||
return array_values($result);
|
||||
}
|
||||
|
||||
|
||||
@@ -52,6 +52,18 @@ hasPush = listen('deck_board_update', (name, body) => {
|
||||
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
|
||||
* provide us with real time updates?
|
||||
|
||||
@@ -333,10 +333,15 @@ export default new Vuex.Store({
|
||||
commit('setAssignableUsers', board.users)
|
||||
},
|
||||
|
||||
async refreshBoard({ commit }, boardId) {
|
||||
async refreshBoard({ commit, dispatch }, boardId) {
|
||||
const board = await apiClient.loadById(boardId)
|
||||
const etagHasChanged = board.ETag !== this.state.currentBoard.ETag
|
||||
commit('setCurrentBoard', board)
|
||||
commit('setAssignableUsers', board.users)
|
||||
|
||||
if (etagHasChanged) {
|
||||
dispatch('loadStacks', boardId)
|
||||
}
|
||||
},
|
||||
|
||||
toggleShowArchived({ commit }) {
|
||||
|
||||
Reference in New Issue
Block a user