diff --git a/appinfo/info.xml b/appinfo/info.xml index 2e534010c..96abbdf62 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -20,7 +20,7 @@ - 🚀 Get your project organized - 4.0.0-dev.1 + 4.0.0-dev.2 agpl Julius Härtl Deck diff --git a/lib/Db/Acl.php b/lib/Db/Acl.php index 735f9a19f..52aba43b1 100644 --- a/lib/Db/Acl.php +++ b/lib/Db/Acl.php @@ -21,6 +21,10 @@ * @method void setOwner(int $owner) * @method void setToken(string $token) * @method string getToken() + * @method int getCreatedAt() + * @method void setCreatedAt(int $createdAt) + * @method int getLastModifiedAt() + * @method void setLastModifiedAt(int $lastModifiedAt) * */ class Acl extends RelationalEntity { @@ -42,6 +46,8 @@ class Acl extends RelationalEntity { protected $permissionManage = false; protected $owner = false; protected $token = null; + protected $createdAt = 0; + protected $lastModifiedAt = 0; public function __construct() { $this->addType('id', 'integer'); @@ -52,6 +58,8 @@ public function __construct() { $this->addType('type', 'integer'); $this->addType('owner', 'boolean'); $this->addType('token', 'string'); + $this->addType('createdAt', 'integer'); + $this->addType('lastModifiedAt', 'integer'); $this->addRelation('owner'); $this->addResolvable('participant'); } diff --git a/lib/Db/AclMapper.php b/lib/Db/AclMapper.php index 669ca7a5e..defba19d1 100644 --- a/lib/Db/AclMapper.php +++ b/lib/Db/AclMapper.php @@ -8,6 +8,7 @@ namespace OCA\Deck\Db; use OCP\AppFramework\Db\DoesNotExistException; +use OCP\AppFramework\Db\Entity; use OCP\AppFramework\Db\MultipleObjectsReturnedException; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IDBConnection; @@ -20,7 +21,7 @@ public function __construct(IDBConnection $db) { public function findByAccessToken(string $accessToken) { $qb = $this->db->getQueryBuilder(); - $qb->select('id', 'board_id', 'type', 'participant', 'permission_edit', 'permission_share', 'permission_manage', 'token') + $qb->select('id', 'board_id', 'type', 'participant', 'permission_edit', 'permission_share', 'permission_manage', 'token', 'created_at', 'last_modified_at') ->from('deck_board_acl') ->where($qb->expr()->eq('token', $qb->createNamedParameter($accessToken, IQueryBuilder::PARAM_STR))) ->setMaxResults(1); @@ -34,7 +35,7 @@ public function findByAccessToken(string $accessToken) { */ public function findAll(int $boardId, ?int $limit = null, ?int $offset = null) { $qb = $this->db->getQueryBuilder(); - $qb->select('id', 'board_id', 'type', 'participant', 'permission_edit', 'permission_share', 'permission_manage', 'token') + $qb->select('id', 'board_id', 'type', 'participant', 'permission_edit', 'permission_share', 'permission_manage', 'token', 'created_at', 'last_modified_at') ->from('deck_board_acl') ->where($qb->expr()->eq('board_id', $qb->createNamedParameter($boardId, IQueryBuilder::PARAM_INT))) ->setMaxResults($limit) @@ -45,7 +46,7 @@ public function findAll(int $boardId, ?int $limit = null, ?int $offset = null) { public function findIn(array $boardIds, ?int $limit = null, ?int $offset = null): array { $qb = $this->db->getQueryBuilder(); - $qb->select('id', 'board_id', 'type', 'participant', 'permission_edit', 'permission_share', 'permission_manage') + $qb->select('id', 'board_id', 'type', 'participant', 'permission_edit', 'permission_share', 'permission_manage', 'created_at', 'last_modified_at') ->from('deck_board_acl') ->where($qb->expr()->in('board_id', $qb->createParameter('boardIds'))) ->setMaxResults($limit) @@ -127,4 +128,18 @@ public function findByType(int $type): array { ->where($qb->expr()->eq('type', $qb->createNamedParameter($type, IQueryBuilder::PARAM_INT))); return $this->findEntities($qb); } + + public function insert(Entity $entity): Entity { + /** @var Acl $entity */ + $now = time(); + $entity->setCreatedAt($now); + $entity->setLastModifiedAt($now); + return parent::insert($entity); + } + + public function update(Entity $entity): Entity { + /** @var Acl $entity */ + $entity->setLastModifiedAt(time()); + return parent::update($entity); + } } diff --git a/lib/Migration/Version11002Date20260611000000.php b/lib/Migration/Version11002Date20260611000000.php new file mode 100644 index 000000000..79b1cd601 --- /dev/null +++ b/lib/Migration/Version11002Date20260611000000.php @@ -0,0 +1,41 @@ +getTable('deck_board_acl'); + + if (!$table->hasColumn('created_at')) { + $table->addColumn('created_at', 'integer', [ + 'notnull' => true, + 'default' => 0, + 'unsigned' => true, + ]); + } + + if (!$table->hasColumn('last_modified_at')) { + $table->addColumn('last_modified_at', 'integer', [ + 'notnull' => true, + 'default' => 0, + 'unsigned' => true, + ]); + } + + return $schema; + } +} diff --git a/lib/Service/BoardService.php b/lib/Service/BoardService.php index 44bc4e29b..1610352ae 100644 --- a/lib/Service/BoardService.php +++ b/lib/Service/BoardService.php @@ -400,6 +400,9 @@ public function addAcl(int $boardId, int $type, $participant, bool $edit, bool $ $acl->setPermissionEdit($edit); $acl->setPermissionShare($share); $acl->setPermissionManage($manage); + $now = time(); + $acl->setCreatedAt($now); + $acl->setLastModifiedAt($now); $newAcl = $this->aclMapper->insert($acl); $this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_BOARD, $newAcl, ActivityManager::SUBJECT_BOARD_SHARE, [], $this->userId); @@ -451,6 +454,7 @@ public function updateAcl(int $id, bool $edit, bool $share, bool $manage): Acl { $acl->setPermissionEdit($edit); $acl->setPermissionShare($share); $acl->setPermissionManage($manage); + $acl->setLastModifiedAt(time()); $this->boardMapper->mapAcl($acl); $acl = $this->aclMapper->update($acl); $this->changeHelper->boardChanged($acl->getBoardId()); diff --git a/tests/integration/base-query-count.txt b/tests/integration/base-query-count.txt index ce8c60422..627806590 100644 --- a/tests/integration/base-query-count.txt +++ b/tests/integration/base-query-count.txt @@ -1 +1 @@ -93102 +96706 \ No newline at end of file diff --git a/tests/integration/import/ImportExportTest.php b/tests/integration/import/ImportExportTest.php index a9e00e0cb..586c11c3a 100644 --- a/tests/integration/import/ImportExportTest.php +++ b/tests/integration/import/ImportExportTest.php @@ -134,7 +134,7 @@ public function testReimportOcc() { ); } - public static function writeArrayStructure(string $prefix = '', array $array = [], array $skipKeyList = ['id', 'boardId', 'cardId', 'stackId', 'ETag', 'permissions', 'shared', 'version', 'done', 'referenceData', 'token']): string { + public static function writeArrayStructure(string $prefix = '', array $array = [], array $skipKeyList = ['id', 'boardId', 'cardId', 'stackId', 'ETag', 'permissions', 'shared', 'version', 'done', 'referenceData', 'token', 'createdAt', 'lastModifiedAt']): string { $output = ''; $arrayIsList = array_keys($array) === range(0, count($array) - 1); foreach ($array as $key => $value) { diff --git a/tests/unit/Db/AclMapperTest.php b/tests/unit/Db/AclMapperTest.php index af3f03196..0154ee62b 100644 --- a/tests/unit/Db/AclMapperTest.php +++ b/tests/unit/Db/AclMapperTest.php @@ -120,6 +120,34 @@ public function testFindBoardIdDatabase() { $this->assertEquals($this->boards[0]->getId(), $this->aclMapper->findBoardId($this->acls[1]->getId())); } + public function testInsertSetsCreatedAtAndLastModifiedAt(): void { + $before = time(); + $acl = $this->getAcl('user', 'timestamps_user', false, false, false, $this->boards[0]->getId()); + $inserted = $this->aclMapper->insert($acl); + $after = time(); + + $this->assertGreaterThanOrEqual($before, $inserted->getCreatedAt()); + $this->assertLessThanOrEqual($after, $inserted->getCreatedAt()); + $this->assertGreaterThanOrEqual($before, $inserted->getLastModifiedAt()); + $this->assertLessThanOrEqual($after, $inserted->getLastModifiedAt()); + + $this->aclMapper->delete($inserted); + } + + public function testUpdateChangesLastModifiedAtButNotCreatedAt(): void { + $acl = $this->getAcl('user', 'timestamps_user2', false, false, false, $this->boards[0]->getId()); + $inserted = $this->aclMapper->insert($acl); + $originalCreatedAt = $inserted->getCreatedAt(); + + $inserted->setPermissionEdit(true); + $updated = $this->aclMapper->update($inserted); + + $this->assertSame($originalCreatedAt, $updated->getCreatedAt()); + $this->assertGreaterThan(0, $updated->getLastModifiedAt()); + + $this->aclMapper->delete($updated); + } + public function tearDown(): void { parent::tearDown(); foreach ($this->acls as $acl) { diff --git a/tests/unit/Db/AclTest.php b/tests/unit/Db/AclTest.php index e1538215e..069cb98dd 100644 --- a/tests/unit/Db/AclTest.php +++ b/tests/unit/Db/AclTest.php @@ -59,7 +59,9 @@ public function testJsonSerialize() { 'permissionEdit' => true, 'permissionShare' => true, 'permissionManage' => true, - 'owner' => false + 'owner' => false, + 'createdAt' => 0, + 'lastModifiedAt' => 0, ], $acl->jsonSerialize()); $acl = $this->createAclGroup(); $this->assertEquals([ @@ -70,7 +72,9 @@ public function testJsonSerialize() { 'permissionEdit' => true, 'permissionShare' => true, 'permissionManage' => true, - 'owner' => false + 'owner' => false, + 'createdAt' => 0, + 'lastModifiedAt' => 0, ], $acl->jsonSerialize()); } @@ -85,7 +89,9 @@ public function testSetOwner() { 'permissionEdit' => true, 'permissionShare' => true, 'permissionManage' => true, - 'owner' => true + 'owner' => true, + 'createdAt' => 0, + 'lastModifiedAt' => 0, ], $acl->jsonSerialize()); } diff --git a/tests/unit/Service/BoardServiceTest.php b/tests/unit/Service/BoardServiceTest.php index 549021bf0..e28a133a7 100644 --- a/tests/unit/Service/BoardServiceTest.php +++ b/tests/unit/Service/BoardServiceTest.php @@ -301,7 +301,16 @@ public function testAddAcl() { ->method('sendBoardShared'); $this->aclMapper->expects($this->once()) ->method('insert') - ->with($acl) + ->with($this->callback(function (Acl $actual) use ($acl) { + return $actual->getBoardId() === $acl->getBoardId() + && $actual->getType() === $acl->getType() + && $actual->getParticipant() === $acl->getParticipant() + && $actual->getPermissionEdit() === $acl->getPermissionEdit() + && $actual->getPermissionShare() === $acl->getPermissionShare() + && $actual->getPermissionManage() === $acl->getPermissionManage() + && $actual->getCreatedAt() > 0 + && $actual->getLastModifiedAt() > 0; + })) ->willReturn($acl); $this->permissionService->expects($this->any()) ->method('findUsers') @@ -407,11 +416,30 @@ public function testAddAclExtendPermission($currentUserAcl, $providedAcl, $resul $expected = clone $acl; $this->aclMapper->expects($this->once()) ->method('insert') - ->with($acl) + ->with($this->callback(function (Acl $actual) use ($acl) { + return $actual->getBoardId() === $acl->getBoardId() + && $actual->getType() === $acl->getType() + && $actual->getParticipant() === $acl->getParticipant() + && $actual->getPermissionEdit() === $acl->getPermissionEdit() + && $actual->getPermissionShare() === $acl->getPermissionShare() + && $actual->getPermissionManage() === $acl->getPermissionManage() + && $actual->getCreatedAt() > 0 + && $actual->getLastModifiedAt() > 0; + })) ->willReturn($acl); $this->eventDispatcher->expects(self::once()) ->method('dispatchTyped') - ->with(new AclCreatedEvent($acl)); + ->with($this->callback(function (AclCreatedEvent $event) use ($acl) { + $eventAcl = $event->getAcl(); + return $eventAcl->getBoardId() === $acl->getBoardId() + && $eventAcl->getType() === $acl->getType() + && $eventAcl->getParticipant() === $acl->getParticipant() + && $eventAcl->getPermissionEdit() === $acl->getPermissionEdit() + && $eventAcl->getPermissionShare() === $acl->getPermissionShare() + && $eventAcl->getPermissionManage() === $acl->getPermissionManage() + && $eventAcl->getCreatedAt() > 0 + && $eventAcl->getLastModifiedAt() > 0; + })); $this->assertEquals($expected, $this->service->addAcl( 123, Acl::PERMISSION_TYPE_USER, 'admin', $providedAcl[0], $providedAcl[1], $providedAcl[2] ));