Skip to content
Merged
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: 0 additions & 2 deletions lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@
use OCA\Polls\Event\ShareLockedEvent;
use OCA\Polls\Event\ShareRegistrationEvent;
use OCA\Polls\Event\ShareTypeChangedEvent;
use OCA\Polls\Event\VoteDeletedOrphanedEvent;
use OCA\Polls\Event\VoteEvent;
use OCA\Polls\Event\VoteSetEvent;
use OCA\Polls\Listener\CommentListener;
Expand Down Expand Up @@ -127,7 +126,6 @@ public function register(IRegistrationContext $context): void {

$context->registerEventListener(VoteEvent::class, VoteListener::class);
$context->registerEventListener(VoteSetEvent::class, VoteListener::class);
$context->registerEventListener(VoteDeletedOrphanedEvent::class, VoteListener::class);
$context->registerEventListener(UserDeletedEvent::class, UserDeletedListener::class);
$context->registerEventListener(GroupDeletedEvent::class, GroupDeletedListener::class);

Expand Down
64 changes: 64 additions & 0 deletions lib/Command/Db/removeOrphanedVotes.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2021 Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Polls\Command\Db;

use Doctrine\DBAL\Schema\Schema;
use OCA\Polls\Command\Command;
use OCA\Polls\Db\IndexManager;
use OCP\IDBConnection;

/**
* @psalm-api
*/
class removeOrphanedVotes extends Command {
protected string $name = parent::NAME_PREFIX . 'index:create';
protected string $description = 'Add all indices and foreign key constraints';
protected array $operationHints = [
'Adds indices and foreing key constraints.',
'NO data migration will be executed, so make sure you have a backup of your database.',
];

public function __construct(
private IndexManager $indexManager,
private IDBConnection $connection,
private Schema $schema,
) {
parent::__construct();
}

protected function runCommands(): int {
// create indices and constraints
// secure, that the schema is updated to the current status
$this->schema = $this->connection->createSchema();
$this->indexManager->setSchema($this->schema);
$this->addForeignKeyConstraints();
$this->addIndices();
$this->connection->migrateToSchema($this->schema);

return 0;
}

/**
* add an on delete fk contraint to all tables referencing the main polls table
*/
private function addForeignKeyConstraints(): void {
$this->printComment('Add foreign key constraints');
$messages = $this->indexManager->createForeignKeyConstraints();
$this->printInfo($messages, ' - ');
}

/**
* Create index for $table
*/
private function addIndices(): void {
$this->printComment('Add indices');
$messages = $this->indexManager->createIndices();
$this->printInfo($messages, ' - ');
}
}
15 changes: 0 additions & 15 deletions lib/Controller/PollApiController.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ public function get(int $pollId): DataResponse {
'poll' => $this->pollService->get($pollId),
'options' => $this->optionService->list($pollId),
'votes' => $this->voteService->list($pollId),
'orphaned' => count($this->voteService->getOprhanedVotes($pollId)),
'comments' => $this->commentService->list($pollId),
'shares' => $this->shareService->list($pollId),
'subscribed' => $this->subscriptionService->get($pollId),
Expand Down Expand Up @@ -196,20 +195,6 @@ public function getParticipantsEmailAddresses(int $pollId): DataResponse {
return $this->response(fn () => ['addresses' => $this->pollService->getParticipantsEmailAddresses($pollId)]);
}

/**
* Delete orphaned votes from pollId
* @param int $pollId poll id
*/
#[CORS]
#[NoAdminRequired]
#[NoCSRFRequired]
#[ApiRoute(verb: 'DELETE', url: '/api/v1.0/poll/{pollId}/votes/orphaned/all', requirements: ['apiVersion' => '(v2)'])]
public function deleteOrphaned(int $pollId): DataResponse {
return $this->response(fn () => [
'deleted' => $this->voteService->deleteOrphanedVotes($pollId)
]);
}

#[CORS]
#[NoAdminRequired]
#[NoCSRFRequired]
Expand Down
21 changes: 1 addition & 20 deletions lib/Controller/PollController.php
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,6 @@ private function getFullPoll(int $pollId, bool $withTimings = false): array {
$votes = $this->voteService->list($pollId);
$timerMicro['votes'] = microtime(true);

$orphaned = $this->voteService->getOprhanedVotes($pollId);
$timerMicro['orphaned'] = microtime(true);

$comments = $this->commentService->list($pollId);
$timerMicro['comments'] = microtime(true);

Expand All @@ -137,8 +134,7 @@ private function getFullPoll(int $pollId, bool $withTimings = false): array {
$diffMicro['poll'] = $timerMicro['poll'] - $timerMicro['start'];
$diffMicro['options'] = $timerMicro['options'] - $timerMicro['poll'];
$diffMicro['votes'] = $timerMicro['votes'] - $timerMicro['options'];
$diffMicro['orphaned'] = $timerMicro['orphaned'] - $timerMicro['votes'];
$diffMicro['comments'] = $timerMicro['comments'] - $timerMicro['orphaned'];
$diffMicro['comments'] = $timerMicro['comments'] - $timerMicro['votes'];
$diffMicro['shares'] = $timerMicro['shares'] - $timerMicro['comments'];
$diffMicro['subscribed'] = $timerMicro['subscribed'] - $timerMicro['shares'];

Expand All @@ -147,7 +143,6 @@ private function getFullPoll(int $pollId, bool $withTimings = false): array {
'poll' => $poll,
'options' => $options,
'votes' => $votes,
'orphaned' => count($orphaned),
'comments' => $comments,
'shares' => $shares,
'subscribed' => $subscribed,
Expand All @@ -158,7 +153,6 @@ private function getFullPoll(int $pollId, bool $withTimings = false): array {
'poll' => $poll,
'options' => $options,
'votes' => $votes,
'orphaned' => count($orphaned),
'comments' => $comments,
'shares' => $shares,
'subscribed' => $subscribed,
Expand Down Expand Up @@ -329,17 +323,4 @@ public function getParticipantsEmailAddresses(int $pollId): JSONResponse {
return $this->response(fn () => $this->pollService->getParticipantsEmailAddresses($pollId));
}

/**
* Delete orphaned votes
* @param int $pollId poll id
*/
#[NoAdminRequired]
#[OpenAPI(OpenAPI::SCOPE_IGNORE)]
#[FrontpageRoute(verb: 'DELETE', url: '/poll/{pollId}/votes/orphaned/all')]
public function deleteOrphaned(int $pollId): JSONResponse {
return $this->response(fn () => [
'deleted' => $this->voteService->deleteOrphanedVotes($pollId)
]);
}

}
7 changes: 6 additions & 1 deletion lib/Cron/JanitorCron.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use OCA\Polls\Db\PollMapper;
use OCA\Polls\Db\ShareMapper;
use OCA\Polls\Db\TableManager;
use OCA\Polls\Db\VoteMapper;
use OCA\Polls\Db\WatchMapper;
use OCA\Polls\Helper\Container;
use OCA\Polls\Model\Settings\AppSettings;
Expand All @@ -39,6 +40,7 @@ public function __construct(
private OptionMapper $optionMapper,
private PollMapper $pollMapper,
private ShareMapper $shareMapper,
private VoteMapper $voteMapper,
private WatchMapper $watchMapper,
private TableManager $tableManager,
) {
Expand All @@ -65,11 +67,14 @@ protected function run($argument) {
// delete entries older than 1 day
$this->watchMapper->deleteOldEntries(time() - 86400);

// purge entries virtually deleted more than 12 hour ago
// purge entries virtually deleted more than 12 hours ago
$deleted['comments'] = $this->commentMapper->purgeDeletedComments(time() - 4320);
$deleted['options'] = $this->optionMapper->purgeDeletedOptions(time() - 4320);
$deleted['shares'] = $this->shareMapper->purgeDeletedShares(time() - 4320);

// purge orphaned votes; Votes without any corresponding option
$deleted['orphaned votes'] = $this->voteMapper->removeOrphanedVotes();

// delete polls after defined days after archiving date
$autoDeleteOffset = $this->appSettings->getAutoDeleteOffsetDays();
if ($this->appSettings->getAutoDeleteEnabled() && $autoDeleteOffset > 0) {
Expand Down
45 changes: 32 additions & 13 deletions lib/Db/VoteMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use OCP\AppFramework\Db\Entity;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
use PDO;
use Psr\Log\LoggerInterface;

/**
Expand Down Expand Up @@ -155,25 +156,43 @@ public function findOrphanedByPollandUser(int $pollId, string $userId): array {
}

/**
* @throws \OCP\AppFramework\Db\DoesNotExistException if not found
* @return Vote[]
* @psalm-return array<array-key, Vote>
* Remove all votes that no more belong to any existing option
*
* @return int Number of deleted votes
*/
public function findOrphanedByPoll(int $pollId): array {
public function removeOrphanedVotes(): int {
$qb = $this->db->getQueryBuilder();

$qb->select(self::TABLE . '.*')
->where($qb->expr()->isNotNull(self::TABLE . '.poll_id'))
->from($this->getTableName(), self::TABLE)
->groupBy(self::TABLE . '.id');
// get ids of votes that have no option
$qb->select('votes.id');
$qb->from($this->getTableName(), 'votes');
$qb->leftJoin(
'votes',
Option::TABLE,
'options',
'votes.poll_id = options.poll_id AND votes.vote_option_text = options.poll_option_text'
);
$qb->where('options.poll_id IS NULL');

$optionAlias = $this->joinOption($qb, self::TABLE);
// get the ids as array
$idsToDelete = $qb->executeQuery()->fetchAll(PDO::FETCH_COLUMN);

$qb->andWhere($qb->expr()->isNull($optionAlias . '.id'));
$qb->andWhere($qb->expr()->eq(self::TABLE . '.poll_id', $qb->createNamedParameter($pollId, IQueryBuilder::PARAM_INT)));
return $this->findEntities($qb);
}
if (empty($idsToDelete)) {
return 0;
}

// delete all votes contained in the id array
$delete = $this->db->getQueryBuilder()
->delete($this->getTableName())
->where('id IN (:ids)')
->setParameter('ids', $idsToDelete, IQueryBuilder::PARAM_INT_ARRAY);

$this->logger->debug('Removing orphaned votes', [
'ids' => $idsToDelete,
]);

return $delete->executeStatement();
}
/**
* Build the enhanced query with joined tables
*/
Expand Down
24 changes: 0 additions & 24 deletions lib/Event/VoteDeletedOrphanedEvent.php

This file was deleted.

38 changes: 0 additions & 38 deletions lib/Service/VoteService.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
use OCA\Polls\Db\PollMapper;
use OCA\Polls\Db\Vote;
use OCA\Polls\Db\VoteMapper;
use OCA\Polls\Event\VoteDeletedOrphanedEvent;
use OCA\Polls\Event\VoteSetEvent;
use OCA\Polls\Exceptions\ForbiddenException;
use OCA\Polls\Exceptions\NotFoundException;
Expand Down Expand Up @@ -133,43 +132,6 @@ public function set(Option|int $optionOrOptionIdoptionId, string $setTo): ?Vote
return $this->vote;
}

/**
* Get all votes of a poll, which are not assigned to an option
*
* @param int $pollId poll id of the poll the votes get deleted from
* @return Vote[]
*/
public function getOprhanedVotes(int $pollId): array {
try {
$this->pollMapper->get($pollId, true, withRoles: true)
->request(Poll::PERMISSION_POLL_EDIT);
return $this->voteMapper->findOrphanedByPoll($pollId);
} catch (ForbiddenException $e) {
return [];
}
}

/**
* Delete all votes of a poll, which are not assigned to an option
*
* @param int $pollId poll id of the poll the votes get deleted from
* @return Vote[]
*/
public function deleteOrphanedVotes(int $pollId): array {
$this->pollMapper->get($pollId, withRoles: true)
->request(Poll::PERMISSION_VOTE_FOREIGN_CHANGE);

// delete all votes of the poll, which are not assigned to an option
$votes = $this->voteMapper->findOrphanedByPoll($pollId);
foreach ($votes as $vote) {
$this->voteMapper->delete($vote);
// TODO: rework notification methods
// keep this dispatch as reminder
// $this->eventDispatcher->dispatchTyped(new VoteDeletedOrphanedEvent($this->vote, false));
}
return $votes;
}

/**
* Remove user from poll
*
Expand Down
1 change: 0 additions & 1 deletion src/Api/modules/api.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ export type FullPollResponse = {
poll: Poll
options: Option[]
votes: Vote[]
orphaned: number
comments: Comment[]
shares: Share[]
subscribed: boolean
Expand Down
15 changes: 1 addition & 14 deletions src/Api/modules/polls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import { Poll, PollConfiguration, PollType } from '../../stores/poll.ts'
import { AxiosResponse } from '@nextcloud/axios'
import { httpInstance, createCancelTokenHandler } from './HttpApi.js'
import { ApiEmailAdressList, Vote } from '../../Types/index.ts'
import { ApiEmailAdressList } from '../../Types/index.ts'
import { PollGroup } from '../../stores/pollGroups.types.ts'
import { FullPollResponse } from './api.types.ts'

Expand Down Expand Up @@ -145,19 +145,6 @@ const polls = {
})
},

removeOrphanedVotes(
pollId: number,
): Promise<AxiosResponse<{ deleted: Vote[] }>> {
return httpInstance.request({
method: 'DELETE',
url: `poll/${pollId}/votes/orphaned/all`,
cancelToken:
cancelTokenHandlerObject[
this.removeOrphanedVotes.name
].handleRequestCancellation().token,
})
},

closePoll(pollId: number): Promise<AxiosResponse<{ poll: Poll }>> {
return httpInstance.request({
method: 'PUT',
Expand Down
Loading