summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--appinfo/info.xml2
-rw-r--r--appinfo/routes/routesRoomController.php2
-rw-r--r--docs/capabilities.md3
-rw-r--r--docs/constants.md4
-rw-r--r--docs/participant.md12
-rw-r--r--lib/Capabilities.php1
-rw-r--r--lib/Controller/RoomController.php12
-rw-r--r--lib/Migration/Version18000Date20230920182747.php55
-rw-r--r--lib/Model/Session.php13
-rw-r--r--lib/Service/ParticipantService.php7
-rw-r--r--lib/Service/SessionService.php12
-rw-r--r--tests/integration/features/bootstrap/FeatureContext.php17
-rw-r--r--tests/integration/features/chat/notifications.feature16
-rw-r--r--tests/php/CapabilitiesTest.php1
14 files changed, 150 insertions, 7 deletions
diff --git a/appinfo/info.xml b/appinfo/info.xml
index 9bb0c815b..3d1b4ff1d 100644
--- a/appinfo/info.xml
+++ b/appinfo/info.xml
@@ -16,7 +16,7 @@ And in the works for the [coming versions](https://github.com/nextcloud/spreed/m
]]></description>
- <version>18.0.0-dev.5</version>
+ <version>18.0.0-dev.6</version>
<licence>agpl</licence>
<author>Daniel Calviño Sánchez</author>
diff --git a/appinfo/routes/routesRoomController.php b/appinfo/routes/routesRoomController.php
index 7542b8884..3127831e1 100644
--- a/appinfo/routes/routesRoomController.php
+++ b/appinfo/routes/routesRoomController.php
@@ -84,6 +84,8 @@ return [
['name' => 'Room#resendInvitations', 'url' => '/api/{apiVersion}/room/{token}/participants/resend-invitations', 'verb' => 'POST', 'requirements' => $requirementsWithToken],
/** @see \OCA\Talk\Controller\RoomController::leaveRoom() */
['name' => 'Room#leaveRoom', 'url' => '/api/{apiVersion}/room/{token}/participants/active', 'verb' => 'DELETE', 'requirements' => $requirementsWithToken],
+ /** @see \OCA\Talk\Controller\RoomController::setSessionState() */
+ ['name' => 'Room#setSessionState', 'url' => '/api/{apiVersion}/room/{token}/participants/state', 'verb' => 'PUT', 'requirements' => $requirementsWithToken],
/** @see \OCA\Talk\Controller\RoomController::promoteModerator() */
['name' => 'Room#promoteModerator', 'url' => '/api/{apiVersion}/room/{token}/moderators', 'verb' => 'POST', 'requirements' => $requirementsWithToken],
/** @see \OCA\Talk\Controller\RoomController::demoteModerator() */
diff --git a/docs/capabilities.md b/docs/capabilities.md
index f953abe00..07f5868eb 100644
--- a/docs/capabilities.md
+++ b/docs/capabilities.md
@@ -126,3 +126,6 @@
* `remind-me-later` - Support for "Remind me later" for chat messages exists
* `bots-v1` - Support of the first version for Bots and Webhooks is available
* `markdown-messages` - Chat messages support markdown and are rendered automatically
+
+## 18
+* `session-state` - Sessions can mark themselves as inactive, so the participant receives notifications again
diff --git a/docs/constants.md b/docs/constants.md
index b46977036..f5c4b2105 100644
--- a/docs/constants.md
+++ b/docs/constants.md
@@ -109,6 +109,10 @@
* `bots` - Used by commands (actor-id is the used `/command`) and the changelog conversation (actor-id is `changelog`)
* `bridged` - Users whose messages are bridged in by the [Matterbridge integration](matterbridge.md)
+### Session states
+* `0` - Inactive (Notifications should still be sent, even though the user has this session in the room)
+* `1` - Active (No notifications should be sent)
+
## Call
### Start call
diff --git a/docs/participant.md b/docs/participant.md
index af875420f..a1bd5fce9 100644
--- a/docs/participant.md
+++ b/docs/participant.md
@@ -160,6 +160,18 @@
+ `403 Forbidden` When the current user is not a moderator or owner
+ `404 Not Found` When the given attendee was not found in the conversation
+## Set session state
+
+* Required capability: `session-state`
+* Method: `PUT`
+* Endpoint: `/room/{token}/participants/state`
+
+* Response:
+ - Status code:
+ + `200 OK`
+ + `400 Bad Request` When the provided state is invalid (see [constants list](constants.md#participant-state))
+ + `404 Not Found` When the conversation could not be found for the participant
+
## Leave a conversation (not available for call and chat anymore)
* Method: `DELETE`
diff --git a/lib/Capabilities.php b/lib/Capabilities.php
index 54e263aff..ec470790b 100644
--- a/lib/Capabilities.php
+++ b/lib/Capabilities.php
@@ -122,6 +122,7 @@ class Capabilities implements IPublicCapability {
'remind-me-later',
'bots-v1',
'markdown-messages',
+ 'session-state',
],
'config' => [
'attachments' => [
diff --git a/lib/Controller/RoomController.php b/lib/Controller/RoomController.php
index e4c959845..3016626ea 100644
--- a/lib/Controller/RoomController.php
+++ b/lib/Controller/RoomController.php
@@ -1226,6 +1226,18 @@ class RoomController extends AEnvironmentAwareController {
}
#[PublicPage]
+ #[RequireParticipant]
+ public function setSessionState(int $state): DataResponse {
+ try {
+ $this->sessionService->updateSessionState($this->participant->getSession(), $state);
+ } catch (\InvalidArgumentException $e) {
+ return new DataResponse([], Http::STATUS_BAD_REQUEST);
+ }
+
+ return new DataResponse();
+ }
+
+ #[PublicPage]
public function leaveRoom(string $token): DataResponse {
$sessionId = $this->session->getSessionForRoom($token);
$this->session->removeSessionForRoom($token);
diff --git a/lib/Migration/Version18000Date20230920182747.php b/lib/Migration/Version18000Date20230920182747.php
new file mode 100644
index 000000000..6a7d96f26
--- /dev/null
+++ b/lib/Migration/Version18000Date20230920182747.php
@@ -0,0 +1,55 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2023, Joas Schilling <coding@schilljs.com>
+ *
+ * @author Joas Schilling <coding@schilljs.com>
+ *
+ * @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\Migration;
+
+use Closure;
+use OCP\DB\ISchemaWrapper;
+use OCP\DB\Types;
+use OCP\Migration\IOutput;
+use OCP\Migration\SimpleMigrationStep;
+
+class Version18000Date20230920182747 extends SimpleMigrationStep {
+ /**
+ * @param IOutput $output
+ * @param Closure(): ISchemaWrapper $schemaClosure
+ * @param array $options
+ * @return null|ISchemaWrapper
+ */
+ public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
+ /** @var ISchemaWrapper $schema */
+ $schema = $schemaClosure();
+
+ $table = $schema->getTable('talk_sessions');
+ if (!$table->hasColumn('state')) {
+ $table->addColumn('state', Types::SMALLINT, [
+ 'default' => 1, // active
+ 'unsigned' => true,
+ ]);
+ return $schema;
+ }
+ return null;
+ }
+}
diff --git a/lib/Model/Session.php b/lib/Model/Session.php
index c2c70e8eb..a6309b950 100644
--- a/lib/Model/Session.php
+++ b/lib/Model/Session.php
@@ -29,10 +29,6 @@ use OCP\AppFramework\Db\Entity;
* A session is the "I'm online in this conversation" state of Talk, you get one
* when opening the conversation while the inCall flag tells if you are just
* online (chatting), or in a call (with audio, camera or even sip).
- * Currently it's limited to 1 per attendee, but the plan is to remove this
- * restriction in the future, so e.g. in the future you can join with your phone
- * on the SIP bridge, have your video/screenshare on the laptop and chat in the
- * mobile app.
*
* @method void setAttendeeId(int $attendeeId)
* @method string getAttendeeId()
@@ -42,8 +38,13 @@ use OCP\AppFramework\Db\Entity;
* @method int getInCall()
* @method void setLastPing(int $lastPing)
* @method int getLastPing()
+ * @method void setState(int $state)
+ * @method int getState()
*/
class Session extends Entity {
+ public const STATE_INACTIVE = 0;
+ public const STATE_ACTIVE = 1;
+
public const SESSION_TIMEOUT = 30;
public const SESSION_TIMEOUT_KILL = self::SESSION_TIMEOUT * 3 + 10;
@@ -59,11 +60,15 @@ class Session extends Entity {
/** @var int */
protected $lastPing;
+ /** @var int */
+ protected $state;
+
public function __construct() {
$this->addType('attendeeId', 'int');
$this->addType('sessionId', 'string');
$this->addType('inCall', 'int');
$this->addType('lastPing', 'int');
+ $this->addType('state', 'int');
}
/**
diff --git a/lib/Service/ParticipantService.php b/lib/Service/ParticipantService.php
index 0f9439796..43cc08610 100644
--- a/lib/Service/ParticipantService.php
+++ b/lib/Service/ParticipantService.php
@@ -1358,10 +1358,13 @@ class ParticipantService {
$helper->selectAttendeesTable($query);
$helper->selectSessionsTable($query);
$query->from('talk_attendees', 'a')
- // Currently we only care if the user has a session at all, so we can select any: #ThisIsFine
+ // Currently we only care if the user has an active session at all, so we can select any
->leftJoin(
'a', 'talk_sessions', 's',
- $query->expr()->eq('s.attendee_id', 'a.id')
+ $query->expr()->andX(
+ $query->expr()->eq('s.attendee_id', 'a.id'),
+ $query->expr()->eq('s.state', $query->createNamedParameter(Session::STATE_ACTIVE, IQueryBuilder::PARAM_INT))
+ )
)
->where($query->expr()->eq('a.room_id', $query->createNamedParameter($room->getId(), IQueryBuilder::PARAM_INT)))
->andWhere($query->expr()->eq('a.notification_level', $query->createNamedParameter($notificationLevel, IQueryBuilder::PARAM_INT)));
diff --git a/lib/Service/SessionService.php b/lib/Service/SessionService.php
index 9ab548926..bb529320e 100644
--- a/lib/Service/SessionService.php
+++ b/lib/Service/SessionService.php
@@ -66,6 +66,18 @@ class SessionService {
}
/**
+ * @throws \InvalidArgumentException
+ */
+ public function updateSessionState(Session $session, int $state): void {
+ if (!in_array($state, [Session::STATE_INACTIVE, Session::STATE_ACTIVE], true)) {
+ throw new \InvalidArgumentException('state');
+ }
+
+ $session->setState($state);
+ $this->sessionMapper->update($session);
+ }
+
+ /**
* @param int[] $ids
*/
public function deleteSessionsById(array $ids): void {
diff --git a/tests/integration/features/bootstrap/FeatureContext.php b/tests/integration/features/bootstrap/FeatureContext.php
index 2881f3b9a..71f8b9226 100644
--- a/tests/integration/features/bootstrap/FeatureContext.php
+++ b/tests/integration/features/bootstrap/FeatureContext.php
@@ -1065,6 +1065,23 @@ class FeatureContext implements Context, SnippetAcceptingContext {
}
/**
+ * @Then /^user "([^"]*)" sets session state to (\d) in room "([^"]*)" with (\d+) \((v4)\)$/
+ *
+ * @param string $user
+ * @param string $identifier
+ * @param int $statusCode
+ * @param string $apiVersion
+ */
+ public function userSessionState(string $user, int $state, string $identifier, int $statusCode, string $apiVersion): void {
+ $this->setCurrentUser($user);
+ $this->sendRequest(
+ 'PUT', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/participants/state',
+ ['state' => $state]
+ );
+ $this->assertStatusCode($this->response, $statusCode);
+ }
+
+ /**
* @Then /^user "([^"]*)" views call-URL of room "([^"]*)" with (\d+)$/
*
* @param string $user
diff --git a/tests/integration/features/chat/notifications.feature b/tests/integration/features/chat/notifications.feature
index 527f6201d..d35fb2f2f 100644
--- a/tests/integration/features/chat/notifications.feature
+++ b/tests/integration/features/chat/notifications.feature
@@ -15,6 +15,22 @@ Feature: chat/notifications
Then user "participant2" has the following notifications
| app | object_type | object_id | subject |
+ Scenario: Normal message when recipient is online but inactive
+ When user "participant1" creates room "one-to-one room" (v4)
+ | roomType | 1 |
+ | invite | participant2 |
+ # Join and leave to clear the invite notification
+ Given user "participant2" joins room "one-to-one room" with 200 (v4)
+ Given user "participant2" sets session state to 2 in room "one-to-one room" with 400 (v4)
+ When user "participant1" sends message "Message 1" to room "one-to-one room" with 201
+ Given user "participant2" sets session state to 0 in room "one-to-one room" with 200 (v4)
+ When user "participant1" sends message "Message 2" to room "one-to-one room" with 201
+ Given user "participant2" sets session state to 1 in room "one-to-one room" with 200 (v4)
+ When user "participant1" sends message "Message 3" to room "one-to-one room" with 201
+ Then user "participant2" has the following notifications
+ | app | object_type | object_id | subject |
+ | spreed | chat | one-to-one room/Message 2 | participant1-displayname sent you a private message |
+
Scenario: Normal message when recipient is offline in the one-to-one
When user "participant1" creates room "one-to-one room" (v4)
| roomType | 1 |
diff --git a/tests/php/CapabilitiesTest.php b/tests/php/CapabilitiesTest.php
index ccb457121..d168c0259 100644
--- a/tests/php/CapabilitiesTest.php
+++ b/tests/php/CapabilitiesTest.php
@@ -140,6 +140,7 @@ class CapabilitiesTest extends TestCase {
'remind-me-later',
'bots-v1',
'markdown-messages',
+ 'session-state',
'message-expiration',
'reactions',
];