-
Notifications
You must be signed in to change notification settings - Fork 34
Initial poll implementation #2113
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
226f5e0
a01f602
803545b
29d6baf
d227f4e
8347286
35575ab
bd4b4e3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| .poll { | ||
| margin: auto; | ||
|
|
||
| form { | ||
| margin: auto; | ||
| max-width: 400px; | ||
| } | ||
|
|
||
| .poll-result { | ||
| margin: .5rem 0; | ||
| } | ||
|
|
||
| .choice-result { | ||
| margin-bottom: .5rem; | ||
|
|
||
| .choice-bar { | ||
| background-color: var(--kbin-button-primary-bg); | ||
| color: var(--kbin-button-primary-text-color); | ||
| height: .5rem; | ||
| border: var(--kbin-button-primary-border); | ||
| border-radius: var(--kbin-rounded-edges-radius); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,6 +23,10 @@ | |
| div { | ||
| margin-bottom: 0; | ||
| } | ||
|
|
||
| .poll-area div { | ||
| margin-bottom: 1rem; | ||
| } | ||
| } | ||
|
|
||
| .post-container { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| poll_vote: | ||
| controller: App\Controller\PollVoteController::vote | ||
| path: /poll/{id}/vote | ||
| methods: [GET] | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because voting is a one-time state change, exposing it as GET lets another site make a logged-in user vote by sending them to a crafted link. Please switch this route and the poll form to POST and validate a CSRF token. |
||
|
|
||
| poll_refresh: | ||
| controller: App\Controller\PollVoteController::refreshVoteCounts | ||
| path: /poll/{id}/refresh | ||
| methods: [GET] | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| api_poll_vote_entry: | ||
| controller: App\Controller\Api\Poll\PollVoteController::voteOnEntry | ||
| path: /api/entry/{entryId}/poll/vote | ||
| methods: [ PUT ] | ||
|
|
||
| api_poll_vote_entry_comment: | ||
| controller: App\Controller\Api\Poll\PollVoteController::voteOnEntryComment | ||
| path: /api/entry/{entryId}/comments/{commentId}/poll/vote | ||
| methods: [ PUT ] | ||
|
|
||
| api_poll_vote_post: | ||
| controller: App\Controller\Api\Poll\PollVoteController::voteOnPost | ||
| path: /api/post/{postId}/poll/vote | ||
| methods: [ PUT ] | ||
|
|
||
| api_poll_vote_post_comment: | ||
| controller: App\Controller\Api\Poll\PollVoteController::voteOnPostComment | ||
| path: /api/post/{postId}/comments/{commentId}/poll/vote | ||
| methods: [ PUT ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace DoctrineMigrations; | ||
|
|
||
| use Doctrine\DBAL\Schema\Schema; | ||
| use Doctrine\Migrations\AbstractMigration; | ||
|
|
||
| final class Version20260408134939 extends AbstractMigration | ||
| { | ||
| public function getDescription(): string | ||
| { | ||
| return 'Create initial poll table and relations'; | ||
| } | ||
|
|
||
| public function up(Schema $schema): void | ||
| { | ||
| $this->addSql('CREATE SEQUENCE poll_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); | ||
| $this->addSql('CREATE SEQUENCE poll_choice_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); | ||
| $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))'); | ||
| $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))'); | ||
| $this->addSql('CREATE INDEX IDX_2DAE19C93C947C0F ON poll_choice (poll_id)'); | ||
| $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))'); | ||
| $this->addSql('CREATE UNIQUE INDEX UNIQ_ED568EBE904F155E ON poll_vote (ap_id)'); | ||
| $this->addSql('CREATE INDEX IDX_ED568EBEEBB4B8AD ON poll_vote (voter_id)'); | ||
| $this->addSql('CREATE INDEX IDX_ED568EBE998666D1 ON poll_vote (choice_id)'); | ||
| $this->addSql('CREATE INDEX IDX_ED568EBE3C947C0F ON poll_vote (poll_id)'); | ||
| $this->addSql('ALTER TABLE poll_choice ADD CONSTRAINT FK_2DAE19C93C947C0F FOREIGN KEY (poll_id) REFERENCES poll (id) ON DELETE CASCADE NOT DEFERRABLE'); | ||
| $this->addSql('ALTER TABLE poll_vote ADD CONSTRAINT FK_ED568EBEEBB4B8AD FOREIGN KEY (voter_id) REFERENCES "user" (id) ON DELETE CASCADE NOT DEFERRABLE'); | ||
| $this->addSql('ALTER TABLE poll_vote ADD CONSTRAINT FK_ED568EBE998666D1 FOREIGN KEY (choice_id) REFERENCES poll_choice (id) ON DELETE CASCADE NOT DEFERRABLE'); | ||
| $this->addSql('ALTER TABLE poll_vote ADD CONSTRAINT FK_ED568EBE3C947C0F FOREIGN KEY (poll_id) REFERENCES poll (id) ON DELETE CASCADE NOT DEFERRABLE'); | ||
| $this->addSql('ALTER TABLE activity ADD object_poll_vote_id UUID DEFAULT NULL'); | ||
| $this->addSql('ALTER TABLE activity ADD CONSTRAINT FK_AC74095A69F3DEA4 FOREIGN KEY (object_poll_vote_id) REFERENCES poll_vote (uuid) ON DELETE CASCADE NOT DEFERRABLE'); | ||
| $this->addSql('CREATE INDEX IDX_AC74095A69F3DEA4 ON activity (object_poll_vote_id)'); | ||
| $this->addSql('ALTER TABLE entry ADD poll_id INT DEFAULT NULL'); | ||
| $this->addSql('ALTER TABLE entry ADD CONSTRAINT FK_2B219D703C947C0F FOREIGN KEY (poll_id) REFERENCES poll (id)'); | ||
| $this->addSql('CREATE UNIQUE INDEX UNIQ_2B219D703C947C0F ON entry (poll_id)'); | ||
| $this->addSql('ALTER TABLE entry_comment ADD poll_id INT DEFAULT NULL'); | ||
| $this->addSql('ALTER TABLE entry_comment ADD CONSTRAINT FK_B892FDFB3C947C0F FOREIGN KEY (poll_id) REFERENCES poll (id)'); | ||
| $this->addSql('CREATE UNIQUE INDEX UNIQ_B892FDFB3C947C0F ON entry_comment (poll_id)'); | ||
| $this->addSql('ALTER TABLE post ADD poll_id INT DEFAULT NULL'); | ||
| $this->addSql('ALTER TABLE post ADD CONSTRAINT FK_5A8A6C8D3C947C0F FOREIGN KEY (poll_id) REFERENCES poll (id)'); | ||
| $this->addSql('CREATE UNIQUE INDEX UNIQ_5A8A6C8D3C947C0F ON post (poll_id)'); | ||
| $this->addSql('ALTER TABLE post_comment ADD poll_id INT DEFAULT NULL'); | ||
| $this->addSql('ALTER TABLE post_comment ADD CONSTRAINT FK_A99CE55F3C947C0F FOREIGN KEY (poll_id) REFERENCES poll (id)'); | ||
| $this->addSql('CREATE UNIQUE INDEX UNIQ_A99CE55F3C947C0F ON post_comment (poll_id)'); | ||
| $this->addSql('ALTER TABLE notification ADD poll_id INT DEFAULT NULL'); | ||
| $this->addSql('ALTER TABLE notification ADD CONSTRAINT FK_BF5476CA3C947C0F FOREIGN KEY (poll_id) REFERENCES poll (id) ON DELETE CASCADE NOT DEFERRABLE'); | ||
| $this->addSql('CREATE INDEX IDX_BF5476CA3C947C0F ON notification (poll_id)'); | ||
| } | ||
|
|
||
| public function down(Schema $schema): void | ||
| { | ||
| $this->addSql('DROP SEQUENCE poll_id_seq CASCADE'); | ||
| $this->addSql('DROP SEQUENCE poll_choice_id_seq CASCADE'); | ||
| $this->addSql('ALTER TABLE activity DROP CONSTRAINT FK_AC74095A69F3DEA4'); | ||
| $this->addSql('DROP INDEX IDX_AC74095A69F3DEA4'); | ||
| $this->addSql('ALTER TABLE activity DROP object_poll_vote_id'); | ||
| $this->addSql('ALTER TABLE entry DROP CONSTRAINT FK_2B219D703C947C0F'); | ||
| $this->addSql('DROP INDEX UNIQ_2B219D703C947C0F'); | ||
| $this->addSql('ALTER TABLE entry DROP poll_id'); | ||
| $this->addSql('ALTER TABLE entry_comment DROP CONSTRAINT FK_B892FDFB3C947C0F'); | ||
| $this->addSql('DROP INDEX UNIQ_B892FDFB3C947C0F'); | ||
| $this->addSql('ALTER TABLE entry_comment DROP poll_id'); | ||
| $this->addSql('ALTER TABLE post DROP CONSTRAINT FK_5A8A6C8D3C947C0F'); | ||
| $this->addSql('DROP INDEX UNIQ_5A8A6C8D3C947C0F'); | ||
| $this->addSql('ALTER TABLE post DROP poll_id'); | ||
| $this->addSql('ALTER TABLE post_comment DROP CONSTRAINT FK_A99CE55F3C947C0F'); | ||
| $this->addSql('ALTER TABLE notification DROP CONSTRAINT FK_BF5476CA3C947C0F'); | ||
| $this->addSql('DROP INDEX IDX_BF5476CA3C947C0F'); | ||
| $this->addSql('ALTER TABLE notification DROP poll_id'); | ||
| $this->addSql('DROP INDEX UNIQ_A99CE55F3C947C0F'); | ||
| $this->addSql('ALTER TABLE post_comment DROP poll_id'); | ||
| $this->addSql('ALTER TABLE poll_choice DROP CONSTRAINT FK_2DAE19C93C947C0F'); | ||
| $this->addSql('ALTER TABLE poll_vote DROP CONSTRAINT FK_ED568EBEEBB4B8AD'); | ||
| $this->addSql('ALTER TABLE poll_vote DROP CONSTRAINT FK_ED568EBE998666D1'); | ||
| $this->addSql('ALTER TABLE poll_vote DROP CONSTRAINT FK_ED568EBE3C947C0F'); | ||
| $this->addSql('DROP TABLE poll'); | ||
| $this->addSql('DROP TABLE poll_choice'); | ||
| $this->addSql('DROP TABLE poll_vote'); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| <?php | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This looks like dev code which should not be in prod. |
||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace App\Command; | ||
|
|
||
| use App\Repository\PollRepository; | ||
| use Symfony\Component\Console\Attribute\AsCommand; | ||
| use Symfony\Component\Console\Command\Command; | ||
| use Symfony\Component\Console\Input\InputInterface; | ||
| use Symfony\Component\Console\Output\OutputInterface; | ||
|
|
||
| #[AsCommand('mbin:debug')] | ||
| class DebugCommand extends Command | ||
| { | ||
| public function __construct( | ||
| private readonly PollRepository $pollRepository, | ||
| ) { | ||
| parent::__construct(); | ||
| } | ||
|
|
||
| protected function execute(InputInterface $input, OutputInterface $output): int | ||
| { | ||
| foreach ($this->pollRepository->getAllEndedPollsToSentNotifications() as $poll) { | ||
| $output->writeln("poll {$poll->getId()}"); | ||
| foreach ($this->pollRepository->getAllLocalVotersOfPoll($poll) as $voter) { | ||
| $output->writeln("Voter $voter->username in poll {$poll->getId()}"); | ||
| } | ||
| } | ||
|
|
||
| return 0; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace App\Controller\ActivityPub; | ||
|
|
||
| use App\Controller\AbstractController; | ||
| use App\Entity\PollVote; | ||
| use App\Entity\User; | ||
| use App\Factory\ActivityPub\PollVoteFactory; | ||
| use Symfony\Bridge\Doctrine\Attribute\MapEntity; | ||
| use Symfony\Component\HttpFoundation\JsonResponse; | ||
| use Symfony\Component\HttpFoundation\Request; | ||
| use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; | ||
|
|
||
| class PollVoteController extends AbstractController | ||
| { | ||
| public function __invoke( | ||
| Request $request, | ||
| PollVoteFactory $pollVoteFactory, | ||
| #[MapEntity(mapping: ['username' => 'username'])] User $user, | ||
| #[MapEntity(mapping: ['uuid' => 'uuid'])] PollVote $pollVote, | ||
| ): JsonResponse { | ||
| if ($pollVote->getUser()->getId() !== $user->getId()) { | ||
| throw new NotFoundHttpException(); | ||
| } | ||
|
|
||
| return new JsonResponse( | ||
| $pollVoteFactory->build($pollVote), | ||
| headers: ['Content-Type' => 'application/activity+json'], | ||
| ); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why centered instead of left-aligned?