Skip to content

Commit

Permalink
feat(polls): Allow moderators to draft polls
Browse files Browse the repository at this point in the history
Signed-off-by: Joas Schilling <[email protected]>
  • Loading branch information
nickvergessen committed Oct 10, 2024
1 parent cd18f1e commit d16811c
Show file tree
Hide file tree
Showing 12 changed files with 690 additions and 26 deletions.
2 changes: 2 additions & 0 deletions appinfo/routes/routesPollController.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
'ocs' => [
/** @see \OCA\Talk\Controller\PollController::createPoll() */
['name' => 'Poll#createPoll', 'url' => '/api/{apiVersion}/poll/{token}', 'verb' => 'POST', 'requirements' => $requirements],
/** @see \OCA\Talk\Controller\PollController::getAllDraftPolls() */
['name' => 'Poll#getAllDraftPolls', 'url' => '/api/{apiVersion}/poll/{token}/drafts', 'verb' => 'GET', 'requirements' => $requirements],
/** @see \OCA\Talk\Controller\PollController::showPoll() */
['name' => 'Poll#showPoll', 'url' => '/api/{apiVersion}/poll/{token}/{pollId}', 'verb' => 'GET', 'requirements' => $requirementsWithPollId],
/** @see \OCA\Talk\Controller\PollController::votePoll() */
Expand Down
1 change: 1 addition & 0 deletions docs/capabilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,4 +158,5 @@

## 20.1
* `archived-conversations` (local) - Conversations can be marked as archived which will hide them from the conversation list by default
* `talk-polls-drafts` - Whether moderators can store and retrieve poll drafts
* `config => call => start-without-media` (local) - Boolean, whether media should be disabled when starting or joining a conversation
1 change: 1 addition & 0 deletions lib/Capabilities.php
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ class Capabilities implements IPublicCapability {
'mention-permissions',
'edit-messages-note-to-self',
'archived-conversations',
'talk-polls-drafts',
];

public const LOCAL_FEATURES = [
Expand Down
101 changes: 77 additions & 24 deletions lib/Controller/PollController.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use OCA\Talk\Exceptions\WrongPermissionsException;
use OCA\Talk\Middleware\Attribute\FederationSupported;
use OCA\Talk\Middleware\Attribute\RequireModeratorOrNoLobby;
use OCA\Talk\Middleware\Attribute\RequireModeratorParticipant;
use OCA\Talk\Middleware\Attribute\RequireParticipant;
use OCA\Talk\Middleware\Attribute\RequirePermission;
use OCA\Talk\Middleware\Attribute\RequireReadWriteConversation;
Expand Down Expand Up @@ -58,6 +59,7 @@ public function __construct(
* @param 0|1 $resultMode Mode how the results will be shown
* @psalm-param Poll::MODE_* $resultMode Mode how the results will be shown
* @param int $maxVotes Number of maximum votes per voter
* @param bool $draft Whether the poll should be saved as a draft (only allowed for moderators and with `talk-polls-drafts` capability)
* @return DataResponse<Http::STATUS_CREATED, TalkPoll, array{}>|DataResponse<Http::STATUS_BAD_REQUEST, array<empty>, array{}>
*
* 201: Poll created successfully
Expand All @@ -69,18 +71,22 @@ public function __construct(
#[RequireParticipant]
#[RequirePermission(permission: RequirePermission::CHAT)]
#[RequireReadWriteConversation]
public function createPoll(string $question, array $options, int $resultMode, int $maxVotes): DataResponse {
public function createPoll(string $question, array $options, int $resultMode, int $maxVotes, bool $draft = false): DataResponse {
if ($this->room->isFederatedConversation()) {
/** @var \OCA\Talk\Federation\Proxy\TalkV1\Controller\PollController $proxy */
$proxy = \OCP\Server::get(\OCA\Talk\Federation\Proxy\TalkV1\Controller\PollController::class);
return $proxy->createPoll($this->room, $this->participant, $question, $options, $resultMode, $maxVotes);
return $proxy->createPoll($this->room, $this->participant, $question, $options, $resultMode, $maxVotes, $draft);
}

if ($this->room->getType() !== Room::TYPE_GROUP
&& $this->room->getType() !== Room::TYPE_PUBLIC) {
return new DataResponse([], Http::STATUS_BAD_REQUEST);
}

if ($draft === true && !$this->participant->hasModeratorPermissions()) {
return new DataResponse([], Http::STATUS_BAD_REQUEST);
}

$attendee = $this->participant->getAttendee();
try {
$poll = $this->pollService->createPoll(
Expand All @@ -91,33 +97,66 @@ public function createPoll(string $question, array $options, int $resultMode, in
$question,
$options,
$resultMode,
$maxVotes
$maxVotes,
$draft,
);
} catch (\Exception $e) {
$this->logger->error('Error creating poll', ['exception' => $e]);
return new DataResponse([], Http::STATUS_BAD_REQUEST);
}

$message = json_encode([
'message' => 'object_shared',
'parameters' => [
'objectType' => 'talk-poll',
'objectId' => $poll->getId(),
'metaData' => [
'type' => 'talk-poll',
'id' => $poll->getId(),
'name' => $question,
]
],
], JSON_THROW_ON_ERROR);
if (!$draft) {
$message = json_encode([
'message' => 'object_shared',
'parameters' => [
'objectType' => 'talk-poll',
'objectId' => $poll->getId(),
'metaData' => [
'type' => 'talk-poll',
'id' => $poll->getId(),
'name' => $question,
]
],
], JSON_THROW_ON_ERROR);

try {
$this->chatManager->addSystemMessage($this->room, $attendee->getActorType(), $attendee->getActorId(), $message, $this->timeFactory->getDateTime(), true);
} catch (\Exception $e) {
$this->logger->error($e->getMessage(), ['exception' => $e]);
try {
$this->chatManager->addSystemMessage($this->room, $attendee->getActorType(), $attendee->getActorId(), $message, $this->timeFactory->getDateTime(), true);
} catch (\Exception $e) {
$this->logger->error($e->getMessage(), ['exception' => $e]);
}
}

return new DataResponse($this->renderPoll($poll, []), Http::STATUS_CREATED);
return new DataResponse($this->renderPoll($poll), Http::STATUS_CREATED);
}

/**
* Get all drafted polls
*
* Required capability: `talk-polls-drafts`
*
* @return DataResponse<Http::STATUS_OK, list<TalkPoll>, array{}>|DataResponse<Http::STATUS_FORBIDDEN|Http::STATUS_NOT_FOUND, list<empty>, array{}>
*
* 200: Poll returned
* 403: User is not a moderator
* 404: Poll not found
*/
#[FederationSupported]
#[PublicPage]
#[RequireModeratorParticipant]
public function getAllDraftPolls(): DataResponse {
if ($this->room->isFederatedConversation()) {
/** @var \OCA\Talk\Federation\Proxy\TalkV1\Controller\PollController $proxy */
$proxy = \OCP\Server::get(\OCA\Talk\Federation\Proxy\TalkV1\Controller\PollController::class);
return $proxy->getDraftsForRoom($this->room, $this->participant);
}

$polls = $this->pollService->getDraftsForRoom($this->room->getId());
$data = [];
foreach ($polls as $poll) {
$data[] = $this->renderPoll($poll);
}

return new DataResponse($data);
}

/**
Expand All @@ -143,7 +182,11 @@ public function showPoll(int $pollId): DataResponse {

try {
$poll = $this->pollService->getPoll($this->room->getId(), $pollId);
} catch (DoesNotExistException $e) {
} catch (DoesNotExistException) {
return new DataResponse([], Http::STATUS_NOT_FOUND);
}

if ($poll->getStatus() === Poll::STATUS_DRAFT && !$this->participant->hasModeratorPermissions()) {
return new DataResponse([], Http::STATUS_NOT_FOUND);
}

Expand Down Expand Up @@ -181,7 +224,11 @@ public function votePoll(int $pollId, array $optionIds = []): DataResponse {

try {
$poll = $this->pollService->getPoll($this->room->getId(), $pollId);
} catch (\Exception $e) {
} catch (DoesNotExistException) {
return new DataResponse([], Http::STATUS_NOT_FOUND);
}

if ($poll->getStatus() === Poll::STATUS_DRAFT) {
return new DataResponse([], Http::STATUS_NOT_FOUND);
}

Expand Down Expand Up @@ -222,9 +269,10 @@ public function votePoll(int $pollId, array $optionIds = []): DataResponse {
*
* @param int $pollId ID of the poll
* @psalm-param non-negative-int $pollId
* @return DataResponse<Http::STATUS_OK, TalkPoll, array{}>|DataResponse<Http::STATUS_BAD_REQUEST|Http::STATUS_FORBIDDEN|Http::STATUS_NOT_FOUND|Http::STATUS_INTERNAL_SERVER_ERROR, array<empty>, array{}>
* @return DataResponse<Http::STATUS_OK, TalkPoll, array{}>|DataResponse<Http::STATUS_ACCEPTED|Http::STATUS_BAD_REQUEST|Http::STATUS_FORBIDDEN|Http::STATUS_NOT_FOUND|Http::STATUS_INTERNAL_SERVER_ERROR, array<empty>, array{}>
*
* 200: Poll closed successfully
* 202: Poll draft was deleted successfully
* 400: Poll already closed
* 403: Missing permissions to close poll
* 404: Poll not found
Expand All @@ -242,10 +290,15 @@ public function closePoll(int $pollId): DataResponse {

try {
$poll = $this->pollService->getPoll($this->room->getId(), $pollId);
} catch (\Exception $e) {
} catch (DoesNotExistException) {
return new DataResponse([], Http::STATUS_NOT_FOUND);
}

if ($poll->getStatus() === Poll::STATUS_DRAFT) {
$this->pollService->deleteByPollId($poll->getId());
return new DataResponse([], Http::STATUS_ACCEPTED);
}

if ($poll->getStatus() === Poll::STATUS_CLOSED) {
return new DataResponse([], Http::STATUS_BAD_REQUEST);
}
Expand Down
35 changes: 34 additions & 1 deletion lib/Federation/Proxy/TalkV1/Controller/PollController.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,38 @@ public function __construct(
) {
}

/**
* @return DataResponse<Http::STATUS_OK, list<TalkPoll>, array{}>|DataResponse<Http::STATUS_FORBIDDEN|Http::STATUS_NOT_FOUND, list<empty>, array{}>
* @throws CannotReachRemoteException
*
* 200: Polls returned
* 404: Polls not found
*
* @see \OCA\Talk\Controller\PollController::showPoll()
*/
public function getDraftsForRoom(Room $room, Participant $participant): DataResponse {
$proxy = $this->proxy->get(
$participant->getAttendee()->getInvitedCloudId(),
$participant->getAttendee()->getAccessToken(),
$room->getRemoteServer() . '/ocs/v2.php/apps/spreed/api/v1/poll/' . $room->getRemoteToken() . '/drafts',
);

$status = $proxy->getStatusCode();
if ($status === Http::STATUS_NOT_FOUND || $status === Http::STATUS_FORBIDDEN) {
return new DataResponse([], $status);
}

/** @var list<TalkPoll> $list */
$list = $this->proxy->getOCSData($proxy);

$data = [];
foreach ($list as $poll) {
$data[] = $this->userConverter->convertPoll($room, $poll);
}

return new DataResponse($data);
}

/**
* @return DataResponse<Http::STATUS_OK, TalkPoll, array{}>|DataResponse<Http::STATUS_NOT_FOUND, array<empty>, array{}>
* @throws CannotReachRemoteException
Expand Down Expand Up @@ -101,7 +133,7 @@ public function votePoll(Room $room, Participant $participant, int $pollId, arra
*
* @see \OCA\Talk\Controller\PollController::createPoll()
*/
public function createPoll(Room $room, Participant $participant, string $question, array $options, int $resultMode, int $maxVotes): DataResponse {
public function createPoll(Room $room, Participant $participant, string $question, array $options, int $resultMode, int $maxVotes, bool $draft): DataResponse {
$proxy = $this->proxy->post(
$participant->getAttendee()->getInvitedCloudId(),
$participant->getAttendee()->getAccessToken(),
Expand All @@ -111,6 +143,7 @@ public function createPoll(Room $room, Participant $participant, string $questio
'options' => $options,
'resultMode' => $resultMode,
'maxVotes' => $maxVotes,
'draft' => $draft,
],
);

Expand Down
1 change: 1 addition & 0 deletions lib/Model/Poll.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
class Poll extends Entity {
public const STATUS_OPEN = 0;
public const STATUS_CLOSED = 1;
public const STATUS_DRAFT = 2;
public const MODE_PUBLIC = 0;
public const MODE_HIDDEN = 1;
public const MAX_VOTES_UNLIMITED = 0;
Expand Down
14 changes: 14 additions & 0 deletions lib/Model/PollMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,20 @@ public function __construct(IDBConnection $db) {
parent::__construct($db, 'talk_polls', Poll::class);
}

/**
* @return Poll[]
*/
public function getDraftsByRoomId(int $roomId): array {
$query = $this->db->getQueryBuilder();

$query->select('*')
->from($this->getTableName())
->where($query->expr()->eq('room_id', $query->createNamedParameter($roomId, IQueryBuilder::PARAM_INT)))
->andWhere($query->expr()->eq('status', $query->createNamedParameter(Poll::STATUS_DRAFT, IQueryBuilder::PARAM_INT)));

return $this->findEntities($query);
}

/**
* @param int $pollId
* @return Poll
Expand Down
13 changes: 12 additions & 1 deletion lib/Service/PollService.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public function __construct(
) {
}

public function createPoll(int $roomId, string $actorType, string $actorId, string $displayName, string $question, array $options, int $resultMode, int $maxVotes): Poll {
public function createPoll(int $roomId, string $actorType, string $actorId, string $displayName, string $question, array $options, int $resultMode, int $maxVotes, bool $draft): Poll {
$question = trim($question);

if ($question === '' || strlen($question) > 32_000) {
Expand Down Expand Up @@ -78,12 +78,23 @@ public function createPoll(int $roomId, string $actorType, string $actorId, stri
$poll->setVotes(json_encode([]));
$poll->setResultMode($resultMode);
$poll->setMaxVotes($maxVotes);
if ($draft) {
$poll->setStatus(Poll::STATUS_DRAFT);
}

$this->pollMapper->insert($poll);

return $poll;
}

/**
* @param int $roomId
* @return Poll[]
*/
public function getDraftsForRoom(int $roomId): array {
return $this->pollMapper->getDraftsByRoomId($roomId);
}

/**
* @param int $roomId
* @param int $pollId
Expand Down
Loading

0 comments on commit d16811c

Please sign in to comment.