Skip to content

Commit 5168de5

Browse files
committed
Poll improvements
- fix the end date being ignored upon poll creation - make the poll end date DATETIMETZ_IMMUTABLE -> with time zone - implement refreshing the vote counts of remote polls - fix handling of `Update` activities -> only edit the poll (and discarding all votes) when the content actually changed, pass through the correct object to refresh the poll counts - send an `Update` activity when a vote is created on a local poll - fix missing query from `EntryCommentRepository` and `PostCommentRepository` in the `PollVoteFactory` - add a way to invalidate the cache of an activity object - add support for the `closed` property on polls (it is the same as `endTime`) - add a way to just show the results of a poll in the UI
1 parent 2879b2e commit 5168de5

21 files changed

Lines changed: 204 additions & 35 deletions

File tree

config/mbin_routes/poll.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,8 @@ poll_vote:
22
controller: App\Controller\PollVoteController::vote
33
path: /poll/{id}/vote
44
methods: [GET]
5+
6+
poll_refresh:
7+
controller: App\Controller\PollVoteController::refreshVoteCounts
8+
path: /poll/{id}/refresh
9+
methods: [GET]

migrations/Version20260408134939.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public function up(Schema $schema): void
1818
{
1919
$this->addSql('CREATE SEQUENCE poll_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
2020
$this->addSql('CREATE SEQUENCE poll_choice_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
21-
$this->addSql('CREATE TABLE poll (id INT NOT NULL, multiple_choice BOOLEAN NOT NULL, voter_count INT DEFAULT 0 NOT NULL, end_date TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, is_remote BOOLEAN NOT NULL, created_at TIMESTAMP(0) WITH TIME ZONE NOT NULL, sent_notifications BOOLEAN NOT NULL, PRIMARY KEY (id))');
21+
$this->addSql('CREATE TABLE poll (id INT NOT NULL, multiple_choice BOOLEAN NOT NULL, voter_count INT DEFAULT 0 NOT NULL, end_date TIMESTAMP(0) WITH TIME ZONE NOT NULL, is_remote BOOLEAN NOT NULL, created_at TIMESTAMP(0) WITH TIME ZONE NOT NULL, sent_notifications BOOLEAN NOT NULL, PRIMARY KEY (id))');
2222
$this->addSql('CREATE TABLE poll_choice (id INT NOT NULL, name VARCHAR(255) NOT NULL, vote_count INT NOT NULL, poll_id INT NOT NULL, PRIMARY KEY (id))');
2323
$this->addSql('CREATE INDEX IDX_2DAE19C93C947C0F ON poll_choice (poll_id)');
2424
$this->addSql('CREATE TABLE poll_vote (uuid UUID NOT NULL, created_at TIMESTAMP(0) WITH TIME ZONE NOT NULL, ap_id VARCHAR(255) DEFAULT NULL, voter_id INT NOT NULL, choice_id INT NOT NULL, poll_id INT NOT NULL, PRIMARY KEY (uuid))');

src/Controller/PollVoteController.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use App\Entity\Poll;
1010
use App\Entity\Post;
1111
use App\Entity\PostComment;
12+
use App\Service\ActivityPub\ApHttpClientInterface;
1213
use App\Service\PollManager;
1314
use Psr\Log\LoggerInterface;
1415
use Symfony\Bridge\Doctrine\Attribute\MapEntity;
@@ -23,6 +24,7 @@ class PollVoteController extends AbstractController
2324
public function __construct(
2425
private readonly PollManager $pollManager,
2526
private readonly LoggerInterface $logger,
27+
private readonly ApHttpClientInterface $apHttpClient,
2628
) {
2729
}
2830

@@ -49,6 +51,28 @@ public function vote(#[MapEntity] Poll $poll, Request $request): Response
4951
throw new BadRequestHttpException(previous: $e);
5052
}
5153

54+
return $this->redirectToPollContent($poll);
55+
}
56+
57+
#[IsGranted('ROLE_USER')]
58+
public function refreshVoteCounts(#[MapEntity] Poll $poll): Response
59+
{
60+
if (null === $poll->getSubject()->apId) {
61+
throw new BadRequestHttpException('Cannot refresh the vote counts of a local poll');
62+
}
63+
64+
$this->apHttpClient->invalidateActivityObjectCache($poll->getSubject()->apId);
65+
$object = $this->apHttpClient->getActivityObject($poll->getSubject()->apId);
66+
if ($this->pollManager->hasPollProperties($object)) {
67+
$this->pollManager->updatePollCounts($poll, $object);
68+
}
69+
70+
return $this->redirectToPollContent($poll);
71+
}
72+
73+
protected function redirectToPollContent(Poll $poll): Response
74+
{
75+
$content = $poll->getSubject();
5276
if ($content instanceof Entry) {
5377
return $this->redirectToRoute('entry_single', ['entry_id' => $content->getId(), 'magazine_name' => $content->magazine->name]);
5478
} elseif ($content instanceof EntryComment) {

src/DTO/PollResponseDto.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,15 @@ class PollResponseDto implements \JsonSerializable
1515
{
1616
public int $voterCount = 0;
1717
public ?bool $currentUserHasVoted = null;
18+
public ?\DateTimeImmutable $endDate = null;
1819
#[OA\Property(type: 'array', items: new OA\Items(ref: new Model(type: PollChoiceResponseDto::class)))]
1920
public ?array $choices = null;
2021

2122
public static function createFromPoll(Poll $poll, User|UserInterface|null $user): self
2223
{
2324
$dto = new PollResponseDto();
2425
$dto->voterCount = $poll->voterCount;
26+
$dto->endDate = $poll->endDate;
2527
$dto->currentUserHasVoted = $user instanceof User ? $poll->hasUserVoted($user) : null;
2628
$dto->choices = array_map(fn (PollChoice $choice) => PollChoiceResponseDto::createFromPollChoice($choice, $user), $poll->choices->toArray());
2729

@@ -32,6 +34,7 @@ public function jsonSerialize(): array
3234
{
3335
return [
3436
'voterCount' => $this->voterCount,
37+
'endDate' => $this->endDate,
3538
'currentUserHasVoted' => $this->currentUserHasVoted,
3639
'choices' => $this->choices ? array_map(fn (PollChoiceResponseDto $dto) => $dto->jsonSerialize(), $this->choices) : null,
3740
];

src/Entity/Poll.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ class Poll
3535
#[Column(options: ['default' => 0])]
3636
public int $voterCount = 0;
3737

38-
#[Column(type: Types::DATETIME_IMMUTABLE)]
38+
#[Column(type: Types::DATETIMETZ_IMMUTABLE)]
3939
public \DateTimeImmutable $endDate;
4040

4141
#[Column]

src/EventSubscriber/PollEventSubscriber.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use App\Event\Poll\PollPreEditedEvent;
99
use App\Event\Poll\PollVoteEvent;
1010
use App\Message\ActivityPub\Outbox\PollVoteMessage;
11+
use App\Message\ActivityPub\Outbox\UpdateMessage;
1112
use App\Service\Notification\PollNotificationManager;
1213
use Psr\Log\LoggerInterface;
1314
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
@@ -38,6 +39,10 @@ public function onPollVote(PollVoteEvent $event): void
3839
foreach ($event->poll->votes as $vote) {
3940
$this->bus->dispatch(new PollVoteMessage($vote->uuid->toString()));
4041
}
42+
} elseif (!$event->poll->isRemote) {
43+
// remote poll -> send update with new vote numbers
44+
$this->bus->dispatch(new UpdateMessage($event->content->getId(), \get_class($event->content)));
45+
$event->content->editedAt = new \DateTimeImmutable();
4146
}
4247
}
4348

src/Factory/ActivityPub/PollVoteFactory.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66

77
use App\Entity\PollVote;
88
use App\Repository\ApActivityRepository;
9+
use App\Repository\EntryCommentRepository;
910
use App\Repository\EntryRepository;
11+
use App\Repository\PostCommentRepository;
1012
use App\Repository\PostRepository;
1113
use App\Service\ActivityPub\ContextsProvider;
1214
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
@@ -17,7 +19,9 @@ public function __construct(
1719
private readonly UrlGeneratorInterface $urlGenerator,
1820
private readonly ContextsProvider $contextsProvider,
1921
private readonly EntryRepository $entryRepository,
22+
private readonly EntryCommentRepository $entryCommentRepository,
2023
private readonly PostRepository $postRepository,
24+
private readonly PostCommentRepository $postCommentRepository,
2125
private readonly PersonFactory $personFactory,
2226
private readonly ApActivityRepository $apActivityRepository,
2327
) {
@@ -26,7 +30,11 @@ public function __construct(
2630
public function build(PollVote $vote, bool $includeContext = true): array
2731
{
2832
$actorUrl = $this->personFactory->getActivityPubId($vote->getUser());
29-
$content = $this->entryRepository->findOneBy(['poll' => $vote->poll]) ?? $this->postRepository->findOneBy(['poll' => $vote->poll]) ?? throw new \LogicException();
33+
$content = $this->entryRepository->findOneBy(['poll' => $vote->poll])
34+
?? $this->entryCommentRepository->findOneBy(['poll' => $vote->poll])
35+
?? $this->postRepository->findOneBy(['poll' => $vote->poll])
36+
?? $this->postCommentRepository->findOneBy(['poll' => $vote->poll])
37+
?? throw new \LogicException();
3038

3139
$result = [
3240
'@context' => $this->contextsProvider->referencedContexts(),

src/Factory/EntryCommentFactory.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ public function createResponseDto(EntryComment $comment, array $tags, int $child
4747
$pollDto = null;
4848
if ($dto->addPoll) {
4949
$pollDto = new PollResponseDto();
50+
$pollDto->endDate = $dto->pollEndsAt;
5051
$pollDto->voterCount = $comment->poll->voterCount;
5152
$user = $this->security->getUser();
5253
$pollDto->currentUserHasVoted = $user instanceof User ? $comment->poll->hasUserVoted($user) : null;

src/Factory/PostCommentFactory.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ public function createResponseDto(PostComment $comment, array $tags, int $childC
4848
$pollDto = null;
4949
if ($dto->addPoll) {
5050
$pollDto = new PollResponseDto();
51+
$pollDto->endDate = $dto->pollEndsAt;
5152
$pollDto->voterCount = $comment->poll->voterCount;
5253
$user = $this->security->getUser();
5354
$pollDto->currentUserHasVoted = $user instanceof User ? $comment->poll->hasUserVoted($user) : null;

src/Factory/PostFactory.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ public function createResponseDto(Post $post, array $tags): PostResponseDto
4343
$pollDto = null;
4444
if ($dto->addPoll) {
4545
$pollDto = new PollResponseDto();
46+
$pollDto->endDate = $dto->pollEndsAt;
4647
$pollDto->voterCount = $post->poll->voterCount;
4748
$user = $this->security->getUser();
4849
$pollDto->currentUserHasVoted = $user instanceof User ? $post->poll->hasUserVoted($user) : null;

0 commit comments

Comments
 (0)