diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml index 64ec93159..f146ddc88 100644 --- a/.github/workflows/phpunit.yml +++ b/.github/workflows/phpunit.yml @@ -66,7 +66,7 @@ jobs: services: mysql: - image: mariadb:10.5 + image: mysql:8.0 ports: - 4444:3306/tcp env: diff --git a/CHANGELOG.md b/CHANGELOG.md index aaa23efae..b20c2ddf3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,14 @@ # Changelog All notable changes to this project will be documented in this file. -## [4.1.3] - 2023-02-23 +## [4.1.4] - 2023-02-23 ### Fix - - Fix infinite updates call, if no polling type for watches were set (avoid server spamming) - - Fix migrations and repair steps + - Fix infinite updates call, if no polling type for watches were set (avoid server spamming) (v4.1.3) + - Fix migrations and repair steps (v4.1.3) + - Fix MySQL error 1071 Specified key was too long; ### changes - - Change default of life update mechanism to manual updates instead of long polling - + - Change default of life update mechanism to manual updates instead of long polling (v4.1.3) + - Added Nextcloud 26 + ## [4.1.2] - 2023-01-23 ### Fix - Invitations are not send out if poll has no description (fix 2) diff --git a/appinfo/info.xml b/appinfo/info.xml index 334dbb82b..fa5c36e90 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -4,7 +4,7 @@ Polls A polls app, similar to Doodle/Dudle with the possibility to restrict access. A polls app, similar to Doodle/Dudle with the possibility to restrict access (members, certain groups/users, hidden and public). - 4.1.3 + 4.1.4 agpl Vinzenz Rosenkranz René Gieling @@ -57,6 +57,7 @@ OCA\Polls\Migration\RepairSteps\RemoveIndices OCA\Polls\Migration\RepairSteps\CreateTables OCA\Polls\Migration\RepairSteps\DeleteInvalidRecords + OCA\Polls\Migration\RepairSteps\UpdateHashes OCA\Polls\Migration\RepairSteps\DropOrphanedTables diff --git a/lib/Command/Db/Rebuild.php b/lib/Command/Db/Rebuild.php index e01864b7a..c8cfa846c 100644 --- a/lib/Command/Db/Rebuild.php +++ b/lib/Command/Db/Rebuild.php @@ -72,8 +72,12 @@ protected function runCommands(): int { $this->createOrUpdateSchema(); $this->tableManager->migrate(); + // validate and fix/create current table layout + $this->printComment('Step 4. set hashes for votes and options'); + $this->migrateOptionsToHash(); + // recreate indices and constraints - $this->printComment('Step 4. Recreate indices and foreign key constraints'); + $this->printComment('Step 5. Recreate indices and foreign key constraints'); // secure, that the schema is updated to the current status $this->indexManager->refreshSchema(); $this->addForeignKeyConstraints(); @@ -132,6 +136,17 @@ private function createOrUpdateSchema(): void { } } + /** + * Add or update hash for votes and options + */ + private function migrateOptionsToHash(): void { + $this->printComment('- add or update hashes'); + $messages = $this->tableManager->migrateOptionsToHash(); + foreach ($messages as $message) { + $this->printInfo(' - ' . $message); + } + } + private function removeObsoleteColumns(): void { $this->printComment('- Drop orphaned columns'); $messages = $this->tableManager->removeObsoleteColumns(); diff --git a/lib/Db/Option.php b/lib/Db/Option.php index 1eb5a3c0a..c58fd96e1 100644 --- a/lib/Db/Option.php +++ b/lib/Db/Option.php @@ -47,6 +47,8 @@ * @method void setPollId(integer $value) * @method string getPollOptionText() * @method void setPollOptionText(string $value) + * @method string getPollOptionHash() + * @method void setPollOptionHash(string $value) * @method int getReleased() * @method void setReleased(int $value) * @method int getTimestamp() @@ -67,6 +69,9 @@ class Option extends EntityWithUser implements JsonSerializable { /** @var string $pollOptionText */ protected $pollOptionText = ''; + /** @var string $pollOptionHash */ + protected $pollOptionHash = ''; + /** @var int $timestamp */ protected $timestamp = 0; diff --git a/lib/Db/OptionMapper.php b/lib/Db/OptionMapper.php index 33abd3135..93b4508e6 100644 --- a/lib/Db/OptionMapper.php +++ b/lib/Db/OptionMapper.php @@ -24,6 +24,7 @@ namespace OCA\Polls\Db; +use OCP\AppFramework\Db\Entity; use OCP\AppFramework\Db\QBMapper; use OCP\DB\Exception; use OCP\DB\QueryBuilder\IQueryBuilder; @@ -40,6 +41,28 @@ public function __construct(IDBConnection $db) { parent::__construct($db, self::TABLE, Option::class); } + public function update(Entity $entity): Entity { + $entity->setPollOptionHash(hash('md5', $entity->getPollId() . $entity->getPollOptionText() . $entity->getTimestamp())); + return parent::update($entity); + } + + public function insert(Entity $entity): Entity { + $entity->setPollOptionHash(hash('md5', $entity->getPollId() . $entity->getPollOptionText() . $entity->getTimestamp())); + return parent::insert($entity); + } + + /** + * @throws \OCP\AppFramework\Db\DoesNotExistException if not found + * @return Option[] + * @psalm-return array + */ + public function getAll(): array { + $qb = $this->db->getQueryBuilder(); + + $qb->select('*')->from($this->getTableName()); + return $this->findEntities($qb); + } + /** * @throws \OCP\AppFramework\Db\DoesNotExistException if not found */ diff --git a/lib/Db/TableManager.php b/lib/Db/TableManager.php index ddf7fd048..acd479aeb 100644 --- a/lib/Db/TableManager.php +++ b/lib/Db/TableManager.php @@ -252,7 +252,7 @@ public function removeOrphaned(): void { public function deleteDuplicates(): array { $messages = []; - if ($this->schema->hasTable(Poll::TABLE)) { + if ($this->schema->hasTable($this->dbPrefix . Poll::TABLE)) { $this->removeOrphaned(); $count[LogMapper::TABLE] = $this->logMapper->removeDuplicates(); @@ -290,9 +290,9 @@ public function removeObsoleteMigrations(): array { } return $messages; } - public function fixVotes() { - if ($this->schema->hasTable(OptionMapper::TABLE)) { - $table = $this->schema->getTable(OptionMapper::TABLE); + public function fixVotes(): void { + if ($this->schema->hasTable($this->dbPrefix . OptionMapper::TABLE)) { + $table = $this->schema->getTable($this->dbPrefix . OptionMapper::TABLE); if ($table->hasColumn('duration')) { $foundOptions = $this->optionMapper->findOptionsWithDuration(); foreach ($foundOptions as $option) { @@ -306,4 +306,37 @@ public function fixVotes() { } } } + + public function migrateOptionsToHash(): array { + $messages = []; + + if ($this->schema->hasTable($this->dbPrefix . OptionMapper::TABLE)) { + $table = $this->schema->getTable($this->dbPrefix . OptionMapper::TABLE); + $count = 0; + if ($table->hasColumn('poll_option_hash')) { + foreach ($this->optionMapper->getAll() as $option) { + $option->setPollOptionHash(hash('md5', $option->getPollId() . $option->getPollOptionText() . $option->getTimestamp())); + + $this->optionMapper->update($option); + $count++; + } + } + $messages[] = 'Updated ' . $count . ' option hashes'; + } + + + if ($this->schema->hasTable($this->dbPrefix . VoteMapper::TABLE)) { + $table = $this->schema->getTable($this->dbPrefix . VoteMapper::TABLE); + $count = 0; + if ($table->hasColumn('vote_option_hash')) { + foreach ($this->voteMapper->getAll() as $vote) { + $vote->setVoteOptionHash(hash('md5', $vote->getPollId() . $vote->getUserId() . $vote->getVoteOptionText())); + $this->voteMapper->update($vote); + $count++; + } + } + $messages[] = 'Updated ' . $count . ' vote hashes'; + } + return $messages; + } } diff --git a/lib/Db/Vote.php b/lib/Db/Vote.php index 9b27a0c34..9bfd4c838 100644 --- a/lib/Db/Vote.php +++ b/lib/Db/Vote.php @@ -36,6 +36,8 @@ * @method void setVoteOptionId(integer $value) * @method string getVoteOptionText() * @method void setVoteOptionText(string $value) + * @method string getVoteOptionHash() + * @method void setVoteOptionHash(string $value) * @method string getVoteAnswer() * @method void setVoteAnswer(string $value) */ @@ -54,6 +56,9 @@ class Vote extends EntityWithUser implements JsonSerializable { /** @var string $voteOptionText */ protected $voteOptionText = ''; + /** @var string $voteOptionHash */ + protected $voteOptionHash = ''; + /** @var string $voteAnswer */ protected $voteAnswer = ''; diff --git a/lib/Db/VoteMapper.php b/lib/Db/VoteMapper.php index b7689b830..5f31c7711 100644 --- a/lib/Db/VoteMapper.php +++ b/lib/Db/VoteMapper.php @@ -24,6 +24,7 @@ namespace OCA\Polls\Db; +use OCP\AppFramework\Db\Entity; use OCP\AppFramework\Db\QBMapper; use OCP\DB\Exception; use OCP\DB\QueryBuilder\IQueryBuilder; @@ -40,6 +41,29 @@ public function __construct(IDBConnection $db) { parent::__construct($db, self::TABLE, Vote::class); } + public function update(Entity $entity): Entity { + $entity->setVoteOptionHash(hash('md5', $entity->getPollId() . $entity->getUserId() . $entity->getVoteOptionText())); + return parent::update($entity); + } + + public function insert(Entity $entity): Entity { + $entity->setVoteOptionHash(hash('md5', $entity->getPollId() . $entity->getUserId() . $entity->getVoteOptionText())); + return parent::insert($entity); + } + + /** + * @throws \OCP\AppFramework\Db\DoesNotExistException if not found + * @return Vote[] + * @psalm-return array + */ + public function getAll(): array { + $qb = $this->db->getQueryBuilder(); + + $qb->select('*')->from($this->getTableName()); + return $this->findEntities($qb); + } + + /** * @throws \OCP\AppFramework\Db\DoesNotExistException if not found * @return Vote[] diff --git a/lib/Migration/RepairSteps/UpdateHashes.php b/lib/Migration/RepairSteps/UpdateHashes.php new file mode 100644 index 000000000..051a6ad94 --- /dev/null +++ b/lib/Migration/RepairSteps/UpdateHashes.php @@ -0,0 +1,52 @@ + + * + * @author René Gieling + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + + +namespace OCA\Polls\Migration\RepairSteps; + +use OCA\Polls\Db\TableManager; +use OCP\Migration\IRepairStep; +use OCP\Migration\IOutput; + +class UpdateHashes implements IRepairStep { + /** @var TableManager */ + private $tableManager; + + public function __construct( + TableManager $tableManager + ) { + $this->tableManager = $tableManager; + } + + public function getName() { + return 'Polls - Create hashes vor votes and options'; + } + + public function run(IOutput $output): void { + // Add hashes to votes and options + $messages = $this->tableManager->migrateOptionsToHash(); + foreach ($messages as $message) { + $output->info($message); + } + } +} diff --git a/lib/Migration/TableSchema.php b/lib/Migration/TableSchema.php index 0391e00d3..859294b62 100644 --- a/lib/Migration/TableSchema.php +++ b/lib/Migration/TableSchema.php @@ -55,11 +55,11 @@ abstract class TableSchema { ]; public const UNIQUE_INDICES = [ - Option::TABLE => ['name' => 'UNIQ_options', 'unique' => true, 'columns' => ['poll_id', 'poll_option_text', 'timestamp']], + Option::TABLE => ['name' => 'UNIQ_options', 'unique' => true, 'columns' => ['poll_id', 'poll_option_hash', 'timestamp']], Log::TABLE => ['name' => 'UNIQ_unprocessed', 'unique' => true, 'columns' => ['processed', 'poll_id', 'user_id', 'message_id']], Subscription::TABLE => ['name' => 'UNIQ_subscription', 'unique' => true, 'columns' => ['poll_id', 'user_id']], Share::TABLE => ['name' => 'UNIQ_shares', 'unique' => true, 'columns' => ['poll_id', 'user_id']], - Vote::TABLE => ['name' => 'UNIQ_votes', 'unique' => true, 'columns' => ['poll_id', 'user_id', 'vote_option_text']], + Vote::TABLE => ['name' => 'UNIQ_votes', 'unique' => true, 'columns' => ['poll_id', 'user_id', 'vote_option_hash']], Preferences::TABLE => ['name' => 'UNIQ_preferences', 'unique' => true, 'columns' => ['user_id']], Watch::TABLE => ['name' => 'UNIQ_watch', 'unique' => true, 'columns' => ['poll_id', 'table', 'session_id']], ]; @@ -172,6 +172,7 @@ abstract class TableSchema { 'id' => ['type' => Types::BIGINT, 'options' => ['autoincrement' => true, 'notnull' => true, 'length' => 20]], 'poll_id' => ['type' => Types::BIGINT, 'options' => ['notnull' => true, 'default' => 0, 'length' => 20]], 'poll_option_text' => ['type' => Types::STRING, 'options' => ['notnull' => false, 'default' => '', 'length' => 1024]], + 'poll_option_hash' => ['type' => Types::STRING, 'options' => ['notnull' => false, 'default' => '', 'length' => 256]], 'timestamp' => ['type' => Types::BIGINT, 'options' => ['notnull' => true, 'default' => 0, 'length' => 20]], 'duration' => ['type' => Types::BIGINT, 'options' => ['notnull' => true, 'default' => 0, 'length' => 20]], 'order' => ['type' => Types::BIGINT, 'options' => ['notnull' => true, 'default' => 0, 'length' => 20]], @@ -185,6 +186,7 @@ abstract class TableSchema { 'user_id' => ['type' => Types::STRING, 'options' => ['notnull' => false, 'default' => '', 'length' => 256]], 'vote_option_id' => ['type' => Types::BIGINT, 'options' => ['notnull' => true, 'default' => 0, 'length' => 20]], 'vote_option_text' => ['type' => Types::STRING, 'options' => ['notnull' => false, 'default' => '', 'length' => 1024]], + 'vote_option_hash' => ['type' => Types::STRING, 'options' => ['notnull' => false, 'default' => '', 'length' => 256]], 'vote_answer' => ['type' => Types::STRING, 'options' => ['notnull' => false, 'default' => '', 'length' => 64]], ], Comment::TABLE => [ diff --git a/package-lock.json b/package-lock.json index efdfe3aa2..f64040976 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "polls", - "version": "4.1.3", + "version": "4.1.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "polls", - "version": "4.1.3", + "version": "4.1.4", "license": "AGPL-3.0", "dependencies": { "@nextcloud/auth": "^2.0.0", diff --git a/package.json b/package.json index 8eba9b025..87f4a801c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "polls", "description": "Polls app for nextcloud", - "version": "4.1.3", + "version": "4.1.4", "authors": [ { "name": "Vinzenz Rosenkranz",