summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.drone.yml130
-rw-r--r--appinfo/routes.php1
-rw-r--r--appinfo/routes/routesReactionController.php33
-rw-r--r--docs/chat.md2
-rw-r--r--docs/index.md1
-rw-r--r--docs/reaction.md20
-rw-r--r--lib/Chat/Parser/Listener.php12
-rw-r--r--lib/Chat/Parser/ReactionParser.php43
-rw-r--r--lib/Chat/ReactionManager.php54
-rw-r--r--lib/Controller/ChatController.php6
-rw-r--r--lib/Controller/ReactionController.php95
-rw-r--r--lib/Model/Message.php2
-rw-r--r--mkdocs.yml1
-rw-r--r--tests/integration/features/bootstrap/FeatureContext.php19
-rw-r--r--tests/integration/features/reaction/react.feature31
-rw-r--r--tests/php/Controller/ChatControllerTest.php5
16 files changed, 454 insertions, 1 deletions
diff --git a/.drone.yml b/.drone.yml
index d25ce0823..78fc3fea9 100644
--- a/.drone.yml
+++ b/.drone.yml
@@ -178,6 +178,43 @@ trigger:
---
kind: pipeline
+name: int-sqlite-reaction
+
+steps:
+ - name: integration-reaction
+ image: ghcr.io/nextcloud/continuous-integration-php8.0:latest
+ environment:
+ APP_NAME: spreed
+ CORE_BRANCH: master
+ GUESTS_BRANCH: master
+ DATABASEHOST: sqlite
+ commands:
+ - bash tests/drone-run-integration-tests.sh || exit 0
+ - wget https://raw.githubusercontent.com/nextcloud/travis_ci/master/before_install.sh
+ - bash ./before_install.sh $APP_NAME $CORE_BRANCH $DATABASEHOST
+ - cd ../server
+ - git clone --depth 1 -b "$GUESTS_BRANCH" https://github.com/nextcloud/guests apps/guests
+ - ./occ app:enable $APP_NAME
+ - cd apps/$APP_NAME
+
+ # Run integration tests
+ - cd tests/integration/
+ - bash run.sh features/reaction
+
+services:
+ - name: cache
+ image: ghcr.io/nextcloud/continuous-integration-redis:latest
+
+trigger:
+ branch:
+ - master
+ - stable*
+ event:
+ - pull_request
+ - push
+
+---
+kind: pipeline
name: int-sqlite-sharing
steps:
@@ -477,6 +514,53 @@ trigger:
---
kind: pipeline
+name: int-mysql-reaction
+
+steps:
+ - name: integration-reaction
+ image: ghcr.io/nextcloud/continuous-integration-php8.0:latest
+ environment:
+ APP_NAME: spreed
+ CORE_BRANCH: master
+ GUESTS_BRANCH: master
+ DATABASEHOST: mysql
+ commands:
+ - bash tests/drone-run-integration-tests.sh || exit 0
+ - wget https://raw.githubusercontent.com/nextcloud/travis_ci/master/before_install.sh
+ - bash ./before_install.sh $APP_NAME $CORE_BRANCH $DATABASEHOST
+ - cd ../server
+ - git clone --depth 1 -b "$GUESTS_BRANCH" https://github.com/nextcloud/guests apps/guests
+ - ./occ app:enable $APP_NAME
+ - cd apps/$APP_NAME
+
+ # Run integration tests
+ - cd tests/integration/
+ - bash run.sh features/reaction
+
+services:
+ - name: cache
+ image: ghcr.io/nextcloud/continuous-integration-redis:latest
+ - name: mysql
+ image: ghcr.io/nextcloud/continuous-integration-mariadb-10.4:10.4
+ environment:
+ MYSQL_ROOT_PASSWORD: owncloud
+ MYSQL_USER: oc_autotest
+ MYSQL_PASSWORD: owncloud
+ MYSQL_DATABASE: oc_autotest
+ command: [ "--innodb_large_prefix=true", "--innodb_file_format=barracuda", "--innodb_file_per_table=true" ]
+ tmpfs:
+ - /var/lib/mysql
+
+trigger:
+ branch:
+ - master
+ - stable*
+ event:
+# - pull_request
+ - push
+
+---
+kind: pipeline
name: int-mysql-sharing
steps:
@@ -791,6 +875,52 @@ trigger:
---
kind: pipeline
+name: int-pgsql-reaction
+
+steps:
+ - name: integration-reaction
+ image: ghcr.io/nextcloud/continuous-integration-php8.0:latest
+ environment:
+ APP_NAME: spreed
+ CORE_BRANCH: master
+ GUESTS_BRANCH: master
+ DATABASEHOST: pgsql
+ commands:
+ - bash tests/drone-run-integration-tests.sh || exit 0
+ - wget https://raw.githubusercontent.com/nextcloud/travis_ci/master/before_install.sh
+ - bash ./before_install.sh $APP_NAME $CORE_BRANCH $DATABASEHOST
+ - cd ../server
+ - git clone --depth 1 -b "$GUESTS_BRANCH" https://github.com/nextcloud/guests apps/guests
+ - ./occ app:enable $APP_NAME
+ - cd apps/$APP_NAME
+
+ # Run integration tests
+ - cd tests/integration/
+ - bash run.sh features/reaction
+
+services:
+ - name: cache
+ image: ghcr.io/nextcloud/continuous-integration-redis:latest
+ - name: pgsql
+ image: ghcr.io/nextcloud/continuous-integration-postgres-13:postgres-13
+ environment:
+ POSTGRES_USER: oc_autotest
+ POSTGRES_DB: oc_autotest_dummy
+ POSTGRES_HOST_AUTH_METHOD: trust
+ POSTGRES_PASSWORD:
+ tmpfs:
+ - /var/lib/postgresql/data
+
+trigger:
+ branch:
+ - master
+ - stable*
+ event:
+# - pull_request
+ - push
+
+---
+kind: pipeline
name: int-pgsql-sharing
steps:
diff --git a/appinfo/routes.php b/appinfo/routes.php
index e888b7006..fde29617e 100644
--- a/appinfo/routes.php
+++ b/appinfo/routes.php
@@ -36,6 +36,7 @@ return array_merge_recursive(
include(__DIR__ . '/routes/routesMatterbridgeSettingsController.php'),
include(__DIR__ . '/routes/routesPageController.php'),
include(__DIR__ . '/routes/routesPublicShareAuthController.php'),
+ include(__DIR__ . '/routes/routesReactionController.php'),
include(__DIR__ . '/routes/routesRoomController.php'),
include(__DIR__ . '/routes/routesSettingsController.php'),
include(__DIR__ . '/routes/routesSignalingController.php'),
diff --git a/appinfo/routes/routesReactionController.php b/appinfo/routes/routesReactionController.php
new file mode 100644
index 000000000..bed62fb15
--- /dev/null
+++ b/appinfo/routes/routesReactionController.php
@@ -0,0 +1,33 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2021 Vitor Mattos <vitor@php.rio>
+ *
+ * @author Vitor Mattos <vitor@php.rio>
+ *
+ * @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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+return [
+ 'ocs' => [
+ ['name' => 'Reaction#react', 'url' => '/api/{apiVersion}/reaction/{token}/{messageId}', 'verb' => 'POST', 'requirements' => [
+ 'apiVersion' => 'v1',
+ 'token' => '^[a-z0-9]{4,30}$',
+ ]],
+ ],
+];
diff --git a/docs/chat.md b/docs/chat.md
index ff03979ef..dd496a7b4 100644
--- a/docs/chat.md
+++ b/docs/chat.md
@@ -50,6 +50,7 @@ Base endpoint is: `/ocs/v2.php/apps/spreed/api/v1`
`message` | string | Message string with placeholders (see [Rich Object String](https://github.com/nextcloud/server/issues/1706))
`messageParameters` | array | Message parameters for `message` (see [Rich Object String](https://github.com/nextcloud/server/issues/1706))
`parent` | array | **Optional:** See `Parent data` below
+ `reactions` | array | **Optional:** An array map with relation between reaction emoji and total of reactions with this emoji
#### Parent data
@@ -324,3 +325,4 @@ See [OCP\RichObjectStrings\Definitions](https://github.com/nextcloud/server/blob
* `matterbridge_config_removed` - {actor} removed the Matterbridge configuration
* `matterbridge_config_enabled` - {actor} started Matterbridge
* `matterbridge_config_disabled` - {actor} stopped Matterbridge
+
diff --git a/docs/index.md b/docs/index.md
index 0e19b823f..3e1dec1dc 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -16,6 +16,7 @@
* [Participant API](participant.md)
* [Call API](call.md)
* [Chat API](chat.md)
+* [Reaction API](reaction.md)
* [Webinar API](webinar.md)
* [Internal Signaling API](internal-signaling.md)
* [Standalone Signaling API](https://nextcloud-spreed-signaling.readthedocs.io/en/latest/)
diff --git a/docs/reaction.md b/docs/reaction.md
new file mode 100644
index 000000000..54a58b75a
--- /dev/null
+++ b/docs/reaction.md
@@ -0,0 +1,20 @@
+# Reaction API
+
+Base endpoint is: `/ocs/v2.php/apps/spreed/api/v1`
+
+## React to a message
+
+* Method: `POST`
+* Endpoint: `/chat/{token}/{messageId}`
+* Data:
+
+ field | type | Description
+ ---|---|---
+ `reaction` | string | the reaction emoji
+
+* Response:
+ - Status code:
+ + `201 Created`
+ + `400 Bad Request` In case of any other error
+ + `404 Not Found` When the conversation or message to react could not be found for the participant
+ + `409 Conflict` User already did this reaction to this message
diff --git a/lib/Chat/Parser/Listener.php b/lib/Chat/Parser/Listener.php
index 83e26f673..593d9bd6e 100644
--- a/lib/Chat/Parser/Listener.php
+++ b/lib/Chat/Parser/Listener.php
@@ -100,6 +100,18 @@ class Listener {
$dispatcher->addListener(MessageParser::EVENT_MESSAGE_PARSE, static function (ChatMessageEvent $event) {
$chatMessage = $event->getMessage();
+ if ($chatMessage->getMessageType() !== 'reaction') {
+ return;
+ }
+
+ /** @var ReactionParser $parser */
+ $parser = \OC::$server->get(ReactionParser::class);
+ $parser->parseMessage($chatMessage);
+ });
+
+ $dispatcher->addListener(MessageParser::EVENT_MESSAGE_PARSE, static function (ChatMessageEvent $event) {
+ $chatMessage = $event->getMessage();
+
if ($chatMessage->getMessageType() !== 'comment_deleted') {
return;
}
diff --git a/lib/Chat/Parser/ReactionParser.php b/lib/Chat/Parser/ReactionParser.php
new file mode 100644
index 000000000..3ec9d8801
--- /dev/null
+++ b/lib/Chat/Parser/ReactionParser.php
@@ -0,0 +1,43 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2021 Vitor Mattos <vitor@php.rio>
+ *
+ * @author Vitor Mattos <vitor@php.rio>
+ *
+ * @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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\Talk\Chat\Parser;
+
+use OCA\Talk\Model\Message;
+
+class ReactionParser {
+ /**
+ * @param Message $message
+ * @throws \OutOfBoundsException
+ */
+ public function parseMessage(Message $message): void {
+ $comment = $message->getComment();
+ if (!in_array($comment->getVerb(), ['reaction'])) {
+ throw new \OutOfBoundsException('Not a reaction');
+ }
+ $message->setMessageType('system');
+ $message->setMessage($message->getMessage(), [], $comment->getVerb());
+ }
+}
diff --git a/lib/Chat/ReactionManager.php b/lib/Chat/ReactionManager.php
new file mode 100644
index 000000000..4ea721a7d
--- /dev/null
+++ b/lib/Chat/ReactionManager.php
@@ -0,0 +1,54 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2021 Vitor Mattos <vitor@php.rio>
+ *
+ * @author Vitor Mattos <vitor@php.rio>
+ *
+ * @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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\Talk\Chat;
+
+use OCA\Talk\Participant;
+use OCA\Talk\Room;
+use OCP\Comments\IComment;
+use OCP\Comments\ICommentsManager;
+
+class ReactionManager {
+ /** @var ICommentsManager|CommentsManager */
+ private $commentsManager;
+
+ public function __construct(CommentsManager $commentsManager) {
+ $this->commentsManager = $commentsManager;
+ }
+
+ public function addReactionMessage(Room $chat, Participant $participant, int $messageId, string $reaction): IComment {
+ $comment = $this->commentsManager->create(
+ $participant->getAttendee()->getActorType(),
+ $participant->getAttendee()->getActorId(),
+ 'chat',
+ (string) $chat->getId()
+ );
+ $comment->setParentId((string) $messageId);
+ $comment->setMessage($reaction);
+ $comment->setVerb('reaction');
+ $this->commentsManager->save($comment);
+ return $comment;
+ }
+}
diff --git a/lib/Controller/ChatController.php b/lib/Controller/ChatController.php
index 312965dc3..e97bc4fc6 100644
--- a/lib/Controller/ChatController.php
+++ b/lib/Controller/ChatController.php
@@ -27,6 +27,7 @@ namespace OCA\Talk\Controller;
use OCA\Talk\Chat\AutoComplete\SearchPlugin;
use OCA\Talk\Chat\AutoComplete\Sorter;
use OCA\Talk\Chat\ChatManager;
+use OCA\Talk\Chat\CommentsManager;
use OCA\Talk\Chat\MessageParser;
use OCA\Talk\GuestManager;
use OCA\Talk\MatterbridgeManager;
@@ -68,6 +69,9 @@ class ChatController extends AEnvironmentAwareController {
/** @var IAppManager */
private $appManager;
+ /** @var CommentsManager */
+ private $commentsManager;
+
/** @var ChatManager */
private $chatManager;
@@ -121,6 +125,7 @@ class ChatController extends AEnvironmentAwareController {
IRequest $request,
IUserManager $userManager,
IAppManager $appManager,
+ CommentsManager $commentsManager,
ChatManager $chatManager,
ParticipantService $participantService,
SessionService $sessionService,
@@ -141,6 +146,7 @@ class ChatController extends AEnvironmentAwareController {
$this->userId = $UserId;
$this->userManager = $userManager;
$this->appManager = $appManager;
+ $this->commentsManager = $commentsManager;
$this->chatManager = $chatManager;
$this->participantService = $participantService;
$this->sessionService = $sessionService;
diff --git a/lib/Controller/ReactionController.php b/lib/Controller/ReactionController.php
new file mode 100644
index 000000000..0343ff061
--- /dev/null
+++ b/lib/Controller/ReactionController.php
@@ -0,0 +1,95 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2021 Vitor Mattos <vitor@php.rio>
+ *
+ * @author Vitor Mattos <vitor@php.rio>
+ *
+ * @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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\Talk\Controller;
+
+use OCA\Talk\Chat\ChatManager;
+use OCA\Talk\Chat\CommentsManager;
+use OCA\Talk\Chat\ReactionManager;
+use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\DataResponse;
+use OCP\Comments\NotFoundException;
+use OCP\IRequest;
+
+class ReactionController extends AEnvironmentAwareController {
+ /** @var CommentsManager */
+ private $commentsManager;
+ /** @var ChatManager */
+ private $chatManager;
+ /** @var ReactionManager */
+ private $reactionManager;
+
+ public function __construct(string $appName,
+ IRequest $request,
+ CommentsManager $commentsManager,
+ ChatManager $chatManager,
+ ReactionManager $reactionManager) {
+ parent::__construct($appName, $request);
+
+ $this->commentsManager = $commentsManager;
+ $this->chatManager = $chatManager;
+ $this->reactionManager = $reactionManager;
+ }
+
+ /**
+ * @NoAdminRequired
+ * @RequireParticipant
+ * @RequireReadWriteConversation
+ * @RequireModeratorOrNoLobby
+ *
+ * @param int $messageId for reaction
+ * @param string $reaction the reaction emoji
+ * @return DataResponse
+ */
+ public function react(int $messageId, string $reaction): DataResponse {
+ $participant = $this->getParticipant();
+ try {
+ // Verify that messageId is part of the room
+ $this->chatManager->getComment($this->getRoom(), (string) $messageId);
+ } catch (NotFoundException $e) {
+ return new DataResponse([], Http::STATUS_NOT_FOUND);
+ }
+
+ try {
+ // Verify already reacted whith the same reaction
+ $this->commentsManager->getReactionComment(
+ $messageId,
+ $participant->getAttendee()->getActorType(),
+ $participant->getAttendee()->getActorId(),
+ $reaction
+ );
+ return new DataResponse([], Http::STATUS_CONFLICT);
+ } catch (NotFoundException $e) {
+ }
+
+ try {
+ $this->reactionManager->addReactionMessage($this->getRoom(), $participant, $messageId, $reaction);
+ } catch (\Exception $e) {
+ return new DataResponse([], Http::STATUS_BAD_REQUEST);
+ }
+
+ return new DataResponse([], Http::STATUS_CREATED);
+ }
+}
diff --git a/lib/Model/Message.php b/lib/Model/Message.php
index 30f1fa6d9..118adad72 100644
--- a/lib/Model/Message.php
+++ b/lib/Model/Message.php
@@ -163,6 +163,7 @@ class Message {
return $this->getMessageType() !== 'system' &&
$this->getMessageType() !== 'command' &&
$this->getMessageType() !== 'comment_deleted' &&
+ $this->getMessageType() !== 'reaction' &&
\in_array($this->getActorType(), [Attendee::ACTOR_USERS, Attendee::ACTOR_GUESTS]);
}
@@ -180,6 +181,7 @@ class Message {
'messageType' => $this->getMessageType(),
'isReplyable' => $this->isReplyable(),
'referenceId' => (string) $this->getComment()->getReferenceId(),
+ 'reactions' => $this->getComment()->getReactions(),
];
if ($this->getMessageType() === 'comment_deleted') {
diff --git a/mkdocs.yml b/mkdocs.yml
index 28860dae3..d83858d64 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -26,6 +26,7 @@ nav:
- 'Participants management': 'participant.md'
- 'Call management': 'call.md'
- 'Chat management': 'chat.md'
+ - 'Reaction management': 'reaction.md'
- 'Webinar management': 'webinar.md'
- 'Settings': 'settings.md'
- 'Integration by other apps': 'integration.md'
diff --git a/tests/integration/features/bootstrap/FeatureContext.php b/tests/integration/features/bootstrap/FeatureContext.php
index 58814dd15..aaf63c134 100644
--- a/tests/integration/features/bootstrap/FeatureContext.php
+++ b/tests/integration/features/bootstrap/FeatureContext.php
@@ -1559,6 +1559,7 @@ class FeatureContext implements Context, SnippetAcceptingContext {
}
$includeParents = in_array('parentMessage', $formData->getRow(0), true);
$includeReferenceId = in_array('referenceId', $formData->getRow(0), true);
+ $includeReactions = in_array('reactions', $formData->getRow(0), true);
$count = count($formData->getHash());
Assert::assertCount($count, $messages, 'Message count does not match');
@@ -1567,7 +1568,7 @@ class FeatureContext implements Context, SnippetAcceptingContext {
$messages[$i]['messageParameters'] = 'IGNORE';
}
}
- Assert::assertEquals($formData->getHash(), array_map(function ($message) use ($includeParents, $includeReferenceId) {
+ Assert::assertEquals($formData->getHash(), array_map(function ($message) use ($includeParents, $includeReferenceId, $includeReactions) {
$data = [
'room' => self::$tokenToIdentifier[$message['token']],
'actorType' => $message['actorType'],
@@ -1584,6 +1585,9 @@ class FeatureContext implements Context, SnippetAcceptingContext {
if ($includeReferenceId) {
$data['referenceId'] = $message['referenceId'];
}
+ if ($includeReactions) {
+ $data['reactions'] = json_encode($message['reactions'], JSON_UNESCAPED_UNICODE);
+ }
return $data;
}, $messages));
}
@@ -2079,6 +2083,19 @@ class FeatureContext implements Context, SnippetAcceptingContext {
$this->setCurrentUser($currentUs