Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
- 🚀 Get your project organized

</description>
<version>2.0.0-dev.0</version>
<version>2.0.0-dev.1</version>
<licence>agpl</licence>
<author>Julius Härtl</author>
<namespace>Deck</namespace>
Expand Down
4 changes: 4 additions & 0 deletions lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
use OCA\Deck\Event\SessionClosedEvent;
use OCA\Deck\Event\SessionCreatedEvent;
use OCA\Deck\Federation\DeckFederationProvider;
use OCA\Deck\Import\BoardImporter;
use OCA\Deck\Import\IBoardImporter;
use OCA\Deck\Listeners\AclCreatedRemovedListener;
use OCA\Deck\Listeners\BeforeTemplateRenderedListener;
use OCA\Deck\Listeners\CommentEventListener;
Expand Down Expand Up @@ -138,6 +140,8 @@ public function register(IRegistrationContext $context): void {
$context->registerMiddleWare(ExceptionMiddleware::class);
$context->registerMiddleWare(DefaultBoardMiddleware::class);

$context->registerServiceAlias(IBoardImporter::class, BoardImporter::class);

$context->registerService('databaseType', static function (ContainerInterface $c) {
return $c->get(IConfig::class)->getSystemValue('dbtype', 'sqlite');
});
Expand Down
76 changes: 76 additions & 0 deletions lib/Db/ImportRef.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Deck\Db;

use OCP\AppFramework\Db\Entity;

/**
* One object an importing app wrote through the board import API, found
* again by the app's reference
*
* @method string getApp()
* @method void setApp(string $app)
* @method string getKind()
* @method void setKind(string $kind)
* @method int getBoardId()
* @method void setBoardId(int $boardId)
* @method string getRef()
* @method void setRef(string $ref)
* @method string getRefHash()
* @method void setRefHash(string $refHash)
* @method int getObjectId()
* @method void setObjectId(int $objectId)
* @method ?string getSourceHash()
* @method void setSourceHash(?string $sourceHash)
* @method ?string getStoredHash()
* @method void setStoredHash(?string $storedHash)
* @method int getState()
* @method void setState(int $state)
* @method int getCreatedAt()
* @method void setCreatedAt(int $createdAt)
* @method int getUpdatedAt()
* @method void setUpdatedAt(int $updatedAt)
*/
class ImportRef extends Entity {
public const KIND_BOARD = 'board';
public const KIND_STACK = 'stack';
public const KIND_LABEL = 'label';
public const KIND_CARD = 'card';
public const KIND_ACL = 'acl';

public const STATE_ACTIVE = 0;
/** The importer moved the object to Deck's deleted items */
public const STATE_REMOVED = 1;

protected $app;
protected $kind;
protected $boardId = 0;
protected $ref;
protected $refHash;
protected $objectId;
protected $sourceHash;
protected $storedHash;
protected $state = self::STATE_ACTIVE;
protected $createdAt = 0;
protected $updatedAt = 0;

public function __construct() {
$this->addType('id', 'integer');
$this->addType('boardId', 'integer');
$this->addType('objectId', 'integer');
$this->addType('state', 'integer');
$this->addType('createdAt', 'integer');
$this->addType('updatedAt', 'integer');
}

public static function hashRef(string $ref): string {
return hash('sha256', $ref);
}
}
96 changes: 96 additions & 0 deletions lib/Db/ImportRefMapper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Deck\Db;

use OCP\AppFramework\Db\QBMapper;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;

/** @template-extends QBMapper<ImportRef> */
class ImportRefMapper extends QBMapper {
public function __construct(IDBConnection $db) {
parent::__construct($db, 'deck_import_refs', ImportRef::class);
}

public function findByRef(string $app, string $kind, int $boardId, string $ref): ?ImportRef {
return $this->findByRefs($app, $kind, $boardId, [$ref])[$ref] ?? null;
}

/**
* @param list<string> $refs
* @return array<string, ImportRef> by ref
*/
public function findByRefs(string $app, string $kind, int $boardId, array $refs): array {
$found = [];
foreach (array_chunk(array_values(array_unique($refs)), 1000) as $chunk) {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->getTableName())
->where($qb->expr()->eq('app', $qb->createNamedParameter($app)))
->andWhere($qb->expr()->eq('kind', $qb->createNamedParameter($kind)))
->andWhere($qb->expr()->eq('board_id', $qb->createNamedParameter($boardId, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->in('ref_hash', $qb->createNamedParameter(array_map(ImportRef::hashRef(...), $chunk), IQueryBuilder::PARAM_STR_ARRAY)));
foreach ($this->findEntities($qb) as $importRef) {
// The hash selects the row, the stored reference confirms it
if (in_array($importRef->getRef(), $chunk, true)) {
$found[$importRef->getRef()] = $importRef;
}
}
}
return $found;
}

/**
* @return list<ImportRef>
*/
public function findAllOfKind(string $app, string $kind, int $boardId): array {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->getTableName())
->where($qb->expr()->eq('app', $qb->createNamedParameter($app)))
->andWhere($qb->expr()->eq('kind', $qb->createNamedParameter($kind)))
->andWhere($qb->expr()->eq('board_id', $qb->createNamedParameter($boardId, IQueryBuilder::PARAM_INT)))
->orderBy('id');
return $this->findEntities($qb);
}

/**
* The reference of a board an importing app created, whichever app it was
*/
public function findBoard(int $boardId): ?ImportRef {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->getTableName())
->where($qb->expr()->eq('kind', $qb->createNamedParameter(ImportRef::KIND_BOARD)))
->andWhere($qb->expr()->eq('object_id', $qb->createNamedParameter($boardId, IQueryBuilder::PARAM_INT)))
->setMaxResults(1);
$entities = $this->findEntities($qb);
return $entities[0] ?? null;
}

/**
* Active card references of a board in the order they were first written
*
* @return list<ImportRef>
*/
public function findCardPage(string $app, int $boardId, int $afterId, int $limit): array {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->getTableName())
->where($qb->expr()->eq('app', $qb->createNamedParameter($app)))
->andWhere($qb->expr()->eq('kind', $qb->createNamedParameter(ImportRef::KIND_CARD)))
->andWhere($qb->expr()->eq('board_id', $qb->createNamedParameter($boardId, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->eq('state', $qb->createNamedParameter(ImportRef::STATE_ACTIVE, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->gt('id', $qb->createNamedParameter($afterId, IQueryBuilder::PARAM_INT)))
->orderBy('id')
->setMaxResults($limit);
return $this->findEntities($qb);
}
}
47 changes: 47 additions & 0 deletions lib/Import/BoardContext.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Deck\Import;

use OCA\Deck\Db\Board;

/**
* A board with the references of its imported stacks and labels that
* still exist
*
* @internal
*/
final class BoardContext {
/**
* @param array<string, int> $stackIds by ref
* @param array<string, int> $labelIds by ref
*/
public function __construct(
public readonly Board $board,
public readonly array $stackIds,
public readonly array $labelIds,
) {
}

/**
* The reference of a stack, or a stand-in for one the importer didn't write
*/
public function stackRef(int $stackId): string {
$ref = array_search($stackId, $this->stackIds, true);
return $ref === false ? 'deck:' . $stackId : (string)$ref;
}

/**
* The reference of a label, or a stand-in for one the importer didn't write
*/
public function labelRef(int $labelId): string {
$ref = array_search($labelId, $this->labelIds, true);
return $ref === false ? 'deck:' . $labelId : (string)$ref;
}
}
Loading
Loading