From f8fef7dc7933db84be1b7f032aede85747bf29e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Fri, 20 Sep 2024 09:56:26 +0200 Subject: [PATCH 1/2] feat(archive): Allow to archive conversations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcel Müller --- appinfo/info.xml | 2 +- appinfo/routes/routesRoomController.php | 4 + docs/capabilities.md | 3 + docs/conversation.md | 1 + lib/Capabilities.php | 2 + lib/Controller/RoomController.php | 30 ++++ .../Version21000Date20240919222538.php | 40 +++++ lib/Model/Attendee.php | 4 + lib/Model/AttendeeMapper.php | 1 + lib/Model/SelectHelper.php | 1 + lib/ResponseDefinitions.php | 1 + lib/Service/ParticipantService.php | 20 +++ lib/Service/RoomFormatter.php | 2 + openapi-backend-sipbridge.json | 6 +- openapi-federation.json | 6 +- openapi-full.json | 168 +++++++++++++++++- openapi.json | 168 +++++++++++++++++- .../openapi/openapi-backend-sipbridge.ts | 1 + src/types/openapi/openapi-federation.ts | 1 + src/types/openapi/openapi-full.ts | 81 +++++++++ src/types/openapi/openapi.ts | 81 +++++++++ .../features/bootstrap/FeatureContext.php | 39 ++++ tests/php/Chat/ChatManagerTest.php | 3 + 23 files changed, 660 insertions(+), 5 deletions(-) create mode 100644 lib/Migration/Version21000Date20240919222538.php diff --git a/appinfo/info.xml b/appinfo/info.xml index c5243f4c29a..fdc4c96394f 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -18,7 +18,7 @@ * 🌉 **Sync with other chat solutions** With [Matterbridge](https://github.com/42wim/matterbridge/) being integrated in Talk, you can easily sync a lot of other chat solutions to Nextcloud Talk and vice-versa. ]]> - 21.0.0-dev.0 + 21.0.0-dev.1 agpl Daniel Calviño Sánchez diff --git a/appinfo/routes/routesRoomController.php b/appinfo/routes/routesRoomController.php index ee176504f7b..0c2d29febbe 100644 --- a/appinfo/routes/routesRoomController.php +++ b/appinfo/routes/routesRoomController.php @@ -111,5 +111,9 @@ ['name' => 'Room#getCapabilities', 'url' => '/api/{apiVersion}/room/{token}/capabilities', 'verb' => 'GET', 'requirements' => $requirementsWithToken], /** @see \OCA\Talk\Controller\RoomController::setMentionPermissions() */ ['name' => 'Room#setMentionPermissions', 'url' => '/api/{apiVersion}/room/{token}/mention-permissions', 'verb' => 'PUT', 'requirements' => $requirementsWithToken], + /** @see \OCA\Talk\Controller\RoomController::archiveConversation() */ + ['name' => 'Room#archiveConversation', 'url' => '/api/{apiVersion}/room/{token}/archive', 'verb' => 'POST', 'requirements' => $requirementsWithToken], + /** @see \OCA\Talk\Controller\RoomController::unarchiveConversation() */ + ['name' => 'Room#unarchiveConversation', 'url' => '/api/{apiVersion}/room/{token}/archive', 'verb' => 'DELETE', 'requirements' => $requirementsWithToken], ], ]; diff --git a/docs/capabilities.md b/docs/capabilities.md index dcb7214801c..7fdd4f639b3 100644 --- a/docs/capabilities.md +++ b/docs/capabilities.md @@ -155,3 +155,6 @@ * `mention-permissions` - Whether non-moderators are allowed to mention `@all` * `federation-v2` - Whether federated session ids are used and calls are possible with federation * `edit-messages-note-to-self` - Messages in note-to-self conversations can be edited indefinitely + +## 20.1 +* `archived-conversations` (local) - Conversations can be marked as archived which will hide them from the conversation list by default diff --git a/docs/conversation.md b/docs/conversation.md index b2e37065bbe..efd24a8fde5 100644 --- a/docs/conversation.md +++ b/docs/conversation.md @@ -110,6 +110,7 @@ | `callRecording` | int | v4 | | Type of call recording (see [Constants - Call recording status](constants.md#call-recording-status)) (only available with `recording-v1` capability) | | `recordingConsent` | int | v4 | | Whether recording consent is required before joining a call (Only 0 and 1 will be returned, see [constants list](constants.md#recording-consent-required)) (only available with `recording-consent` capability) | | `mentionPermissions` | int | v4 | | Whether all participants can mention using `@all` or only moderators (see [constants list](constants.md#mention-permissions)) (only available with `mention-permissions` capability) | +| `isArchived` | bool | v4 | | Flag if the conversation is archived by the user (only available with `archived-conversations` capability) | | ## Creating a new conversation diff --git a/lib/Capabilities.php b/lib/Capabilities.php index 5df082d7391..0cff6f89ae4 100644 --- a/lib/Capabilities.php +++ b/lib/Capabilities.php @@ -103,6 +103,7 @@ class Capabilities implements IPublicCapability { 'chat-reference-id', 'mention-permissions', 'edit-messages-note-to-self', + 'archived-conversations', ]; public const LOCAL_FEATURES = [ @@ -115,6 +116,7 @@ class Capabilities implements IPublicCapability { 'avatar', 'remind-me-later', 'note-to-self', + 'archived-conversations', ]; public const LOCAL_CONFIGS = [ diff --git a/lib/Controller/RoomController.php b/lib/Controller/RoomController.php index bbb38774574..d8b47b9604f 100644 --- a/lib/Controller/RoomController.php +++ b/lib/Controller/RoomController.php @@ -1543,6 +1543,36 @@ public function setPassword(string $password): DataResponse { return new DataResponse(); } + /** + * Archive a conversation + * + * @return DataResponse + * + * 200: Conversation was archived + */ + #[NoAdminRequired] + #[FederationSupported] + #[RequireLoggedInParticipant] + public function archiveConversation(): DataResponse { + $this->participantService->archiveConversation($this->participant); + return new DataResponse($this->formatRoom($this->room, $this->participant)); + } + + /** + * Unarchive a conversation + * + * @return DataResponse + * + * 200: Conversation was unarchived + */ + #[NoAdminRequired] + #[FederationSupported] + #[RequireLoggedInParticipant] + public function unarchiveConversation(): DataResponse { + $this->participantService->unarchiveConversation($this->participant); + return new DataResponse($this->formatRoom($this->room, $this->participant)); + } + /** * Join a room * diff --git a/lib/Migration/Version21000Date20240919222538.php b/lib/Migration/Version21000Date20240919222538.php new file mode 100644 index 00000000000..a5e01731f6c --- /dev/null +++ b/lib/Migration/Version21000Date20240919222538.php @@ -0,0 +1,40 @@ +getTable('talk_attendees'); + if (!$table->hasColumn('archived')) { + $table->addColumn('archived', Types::BOOLEAN, [ + 'default' => 0, + 'notnull' => false, + ]); + } + + return $schema; + } +} diff --git a/lib/Model/Attendee.php b/lib/Model/Attendee.php index 2682c9b44f9..74527b32632 100644 --- a/lib/Model/Attendee.php +++ b/lib/Model/Attendee.php @@ -39,6 +39,8 @@ * @method void setReadPrivacy(int $readPrivacy) * @method int getReadPrivacy() * @method void setPermissions(int $permissions) + * @method void setArchived(bool $archived) + * @method bool isArchived() * @internal * @method int getPermissions() * @method void setAccessToken(string $accessToken) @@ -108,6 +110,7 @@ class Attendee extends Entity { protected bool $favorite = false; protected int $notificationLevel = 0; protected int $notificationCalls = 0; + protected bool $archived = false; protected int $lastJoinedCall = 0; protected int $lastReadMessage = 0; protected int $lastMentionMessage = 0; @@ -131,6 +134,7 @@ public function __construct() { $this->addType('pin', 'string'); $this->addType('participantType', 'int'); $this->addType('favorite', 'bool'); + $this->addType('archived', 'bool'); $this->addType('notificationLevel', 'int'); $this->addType('notificationCalls', 'int'); $this->addType('lastJoinedCall', 'int'); diff --git a/lib/Model/AttendeeMapper.php b/lib/Model/AttendeeMapper.php index 2448942ed69..100f1a7a5b1 100644 --- a/lib/Model/AttendeeMapper.php +++ b/lib/Model/AttendeeMapper.php @@ -324,6 +324,7 @@ public function createAttendeeFromRow(array $row): Attendee { 'state' => (int)$row['state'], 'unread_messages' => (int)$row['unread_messages'], 'last_attendee_activity' => (int)$row['last_attendee_activity'], + 'archived' => (bool)$row['archived'], ]); } } diff --git a/lib/Model/SelectHelper.php b/lib/Model/SelectHelper.php index c3022c4445f..14f5dc36f96 100644 --- a/lib/Model/SelectHelper.php +++ b/lib/Model/SelectHelper.php @@ -76,6 +76,7 @@ public function selectAttendeesTable(IQueryBuilder $query, string $alias = 'a'): ->addSelect($alias . 'state') ->addSelect($alias . 'unread_messages') ->addSelect($alias . 'last_attendee_activity') + ->addSelect($alias . 'archived') ->selectAlias($alias . 'id', 'a_id'); } diff --git a/lib/ResponseDefinitions.php b/lib/ResponseDefinitions.php index f690e01829e..7344e078f6c 100644 --- a/lib/ResponseDefinitions.php +++ b/lib/ResponseDefinitions.php @@ -278,6 +278,7 @@ * unreadMention: bool, * unreadMentionDirect: bool, * unreadMessages: int, + * isArchived: bool, * } * * @psalm-type TalkSignalingSession = array{ diff --git a/lib/Service/ParticipantService.php b/lib/Service/ParticipantService.php index b89d2e9e257..71ba2e4f0c5 100644 --- a/lib/Service/ParticipantService.php +++ b/lib/Service/ParticipantService.php @@ -298,6 +298,26 @@ public function updateNotificationCalls(Participant $participant, int $level): v $this->attendeeMapper->update($attendee); } + /** + * @param Participant $participant + */ + public function archiveConversation(Participant $participant): void { + $attendee = $participant->getAttendee(); + $attendee->setArchived(true); + $attendee->setLastAttendeeActivity($this->timeFactory->getTime()); + $this->attendeeMapper->update($attendee); + } + + /** + * @param Participant $participant + */ + public function unarchiveConversation(Participant $participant): void { + $attendee = $participant->getAttendee(); + $attendee->setArchived(false); + $attendee->setLastAttendeeActivity($this->timeFactory->getTime()); + $this->attendeeMapper->update($attendee); + } + /** * @param RoomService $roomService * @param Room $room diff --git a/lib/Service/RoomFormatter.php b/lib/Service/RoomFormatter.php index 92d9cee65bf..eeab3e1ab8b 100644 --- a/lib/Service/RoomFormatter.php +++ b/lib/Service/RoomFormatter.php @@ -140,6 +140,7 @@ public function formatRoomV4( 'breakoutRoomStatus' => BreakoutRoom::STATUS_STOPPED, 'recordingConsent' => $this->talkConfig->recordingConsentRequired() === RecordingService::CONSENT_REQUIRED_OPTIONAL ? $room->getRecordingConsent() : $this->talkConfig->recordingConsentRequired(), 'mentionPermissions' => Room::MENTION_PERMISSIONS_EVERYONE, + 'isArchived' => false, ]; if ($room->isFederatedConversation()) { @@ -223,6 +224,7 @@ public function formatRoomV4( 'breakoutRoomMode' => $room->getBreakoutRoomMode(), 'breakoutRoomStatus' => $room->getBreakoutRoomStatus(), 'mentionPermissions' => $room->getMentionPermissions(), + 'isArchived' => $attendee->isArchived(), ]); if ($room->isFederatedConversation()) { diff --git a/openapi-backend-sipbridge.json b/openapi-backend-sipbridge.json index de382613f1b..9dd9c985c56 100644 --- a/openapi-backend-sipbridge.json +++ b/openapi-backend-sipbridge.json @@ -554,7 +554,8 @@ "type", "unreadMention", "unreadMentionDirect", - "unreadMessages" + "unreadMessages", + "isArchived" ], "properties": { "actorId": { @@ -769,6 +770,9 @@ "unreadMessages": { "type": "integer", "format": "int64" + }, + "isArchived": { + "type": "boolean" } } }, diff --git a/openapi-federation.json b/openapi-federation.json index 7e9364ada09..2b7b8a278b5 100644 --- a/openapi-federation.json +++ b/openapi-federation.json @@ -608,7 +608,8 @@ "type", "unreadMention", "unreadMentionDirect", - "unreadMessages" + "unreadMessages", + "isArchived" ], "properties": { "actorId": { @@ -823,6 +824,9 @@ "unreadMessages": { "type": "integer", "format": "int64" + }, + "isArchived": { + "type": "boolean" } } }, diff --git a/openapi-full.json b/openapi-full.json index 57cd23cf210..6fe5a4e43db 100644 --- a/openapi-full.json +++ b/openapi-full.json @@ -1142,7 +1142,8 @@ "type", "unreadMention", "unreadMentionDirect", - "unreadMessages" + "unreadMessages", + "isArchived" ], "properties": { "actorId": { @@ -1357,6 +1358,9 @@ "unreadMessages": { "type": "integer", "format": "int64" + }, + "isArchived": { + "type": "boolean" } } }, @@ -15806,6 +15810,168 @@ } } }, + "/ocs/v2.php/apps/spreed/api/{apiVersion}/room/{token}/archive": { + "post": { + "operationId": "room-archive-conversation", + "summary": "Archive a conversation", + "tags": [ + "room" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "apiVersion", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "v4" + ], + "default": "v4" + } + }, + { + "name": "token", + "in": "path", + "required": true, + "schema": { + "type": "string", + "pattern": "^[a-z0-9]{4,30}$" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Conversation was archived", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "$ref": "#/components/schemas/Room" + } + } + } + } + } + } + } + } + } + }, + "delete": { + "operationId": "room-unarchive-conversation", + "summary": "Unarchive a conversation", + "tags": [ + "room" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "apiVersion", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "v4" + ], + "default": "v4" + } + }, + { + "name": "token", + "in": "path", + "required": true, + "schema": { + "type": "string", + "pattern": "^[a-z0-9]{4,30}$" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Conversation was unarchived", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "$ref": "#/components/schemas/Room" + } + } + } + } + } + } + } + } + } + } + }, "/ocs/v2.php/apps/spreed/api/{apiVersion}/settings/user": { "post": { "operationId": "settings-set-user-setting", diff --git a/openapi.json b/openapi.json index 3e3a6b48a4f..fcab24e35c5 100644 --- a/openapi.json +++ b/openapi.json @@ -1029,7 +1029,8 @@ "type", "unreadMention", "unreadMentionDirect", - "unreadMessages" + "unreadMessages", + "isArchived" ], "properties": { "actorId": { @@ -1244,6 +1245,9 @@ "unreadMessages": { "type": "integer", "format": "int64" + }, + "isArchived": { + "type": "boolean" } } }, @@ -15940,6 +15944,168 @@ } } }, + "/ocs/v2.php/apps/spreed/api/{apiVersion}/room/{token}/archive": { + "post": { + "operationId": "room-archive-conversation", + "summary": "Archive a conversation", + "tags": [ + "room" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "apiVersion", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "v4" + ], + "default": "v4" + } + }, + { + "name": "token", + "in": "path", + "required": true, + "schema": { + "type": "string", + "pattern": "^[a-z0-9]{4,30}$" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Conversation was archived", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "$ref": "#/components/schemas/Room" + } + } + } + } + } + } + } + } + } + }, + "delete": { + "operationId": "room-unarchive-conversation", + "summary": "Unarchive a conversation", + "tags": [ + "room" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "apiVersion", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "v4" + ], + "default": "v4" + } + }, + { + "name": "token", + "in": "path", + "required": true, + "schema": { + "type": "string", + "pattern": "^[a-z0-9]{4,30}$" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Conversation was unarchived", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "$ref": "#/components/schemas/Room" + } + } + } + } + } + } + } + } + } + } + }, "/ocs/v2.php/apps/spreed/api/{apiVersion}/settings/user": { "post": { "operationId": "settings-set-user-setting", diff --git a/src/types/openapi/openapi-backend-sipbridge.ts b/src/types/openapi/openapi-backend-sipbridge.ts index 7b16c3f74b7..e52a228156f 100644 --- a/src/types/openapi/openapi-backend-sipbridge.ts +++ b/src/types/openapi/openapi-backend-sipbridge.ts @@ -332,6 +332,7 @@ export type components = { unreadMentionDirect: boolean; /** Format: int64 */ unreadMessages: number; + isArchived: boolean; }; RoomLastMessage: components["schemas"]["ChatMessage"] | components["schemas"]["ChatProxyMessage"]; }; diff --git a/src/types/openapi/openapi-federation.ts b/src/types/openapi/openapi-federation.ts index 980015022a6..1aabd84c1f2 100644 --- a/src/types/openapi/openapi-federation.ts +++ b/src/types/openapi/openapi-federation.ts @@ -379,6 +379,7 @@ export type components = { unreadMentionDirect: boolean; /** Format: int64 */ unreadMessages: number; + isArchived: boolean; }; RoomLastMessage: components["schemas"]["ChatMessage"] | components["schemas"]["ChatProxyMessage"]; }; diff --git a/src/types/openapi/openapi-full.ts b/src/types/openapi/openapi-full.ts index dd7cc552370..8cbdba663d4 100644 --- a/src/types/openapi/openapi-full.ts +++ b/src/types/openapi/openapi-full.ts @@ -1222,6 +1222,24 @@ export type paths = { patch?: never; trace?: never; }; + "/ocs/v2.php/apps/spreed/api/{apiVersion}/room/{token}/archive": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Archive a conversation */ + post: operations["room-archive-conversation"]; + /** Unarchive a conversation */ + delete: operations["room-unarchive-conversation"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/ocs/v2.php/apps/spreed/api/{apiVersion}/settings/user": { parameters: { query?: never; @@ -2175,6 +2193,7 @@ export type components = { unreadMentionDirect: boolean; /** Format: int64 */ unreadMessages: number; + isArchived: boolean; }; RoomLastMessage: components["schemas"]["ChatMessage"] | components["schemas"]["ChatProxyMessage"]; SignalingSession: { @@ -7994,6 +8013,68 @@ export interface operations { }; }; }; + "room-archive-conversation": { + parameters: { + query?: never; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path: { + apiVersion: "v4"; + token: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Conversation was archived */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: components["schemas"]["Room"]; + }; + }; + }; + }; + }; + }; + "room-unarchive-conversation": { + parameters: { + query?: never; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path: { + apiVersion: "v4"; + token: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Conversation was unarchived */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: components["schemas"]["Room"]; + }; + }; + }; + }; + }; + }; "settings-set-user-setting": { parameters: { query?: never; diff --git a/src/types/openapi/openapi.ts b/src/types/openapi/openapi.ts index d2ec65d2d52..594f0479e2e 100644 --- a/src/types/openapi/openapi.ts +++ b/src/types/openapi/openapi.ts @@ -1224,6 +1224,24 @@ export type paths = { patch?: never; trace?: never; }; + "/ocs/v2.php/apps/spreed/api/{apiVersion}/room/{token}/archive": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Archive a conversation */ + post: operations["room-archive-conversation"]; + /** Unarchive a conversation */ + delete: operations["room-unarchive-conversation"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/ocs/v2.php/apps/spreed/api/{apiVersion}/settings/user": { parameters: { query?: never; @@ -1656,6 +1674,7 @@ export type components = { unreadMentionDirect: boolean; /** Format: int64 */ unreadMessages: number; + isArchived: boolean; }; RoomLastMessage: components["schemas"]["ChatMessage"] | components["schemas"]["ChatProxyMessage"]; SignalingSession: { @@ -7575,6 +7594,68 @@ export interface operations { }; }; }; + "room-archive-conversation": { + parameters: { + query?: never; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path: { + apiVersion: "v4"; + token: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Conversation was archived */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: components["schemas"]["Room"]; + }; + }; + }; + }; + }; + }; + "room-unarchive-conversation": { + parameters: { + query?: never; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path: { + apiVersion: "v4"; + token: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Conversation was unarchived */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: components["schemas"]["Room"]; + }; + }; + }; + }; + }; + }; "settings-set-user-setting": { parameters: { query?: never; diff --git a/tests/integration/features/bootstrap/FeatureContext.php b/tests/integration/features/bootstrap/FeatureContext.php index 3c92b9b16f6..0b2dff7a1f3 100644 --- a/tests/integration/features/bootstrap/FeatureContext.php +++ b/tests/integration/features/bootstrap/FeatureContext.php @@ -4828,6 +4828,45 @@ public function userSetsMentionPermissionsOfTheRoom(string $user, string $identi $this->assertStatusCode($this->response, $statusCode); } + /** + * @When /^user "([^"]*)" (unarchives|archives) room "([^"]*)" with (\d+) \((v4)\)$/ + * + * @param string $user + * @param string $identifier + * @param string $action + * @param int $statusCode + * @param string $apiVersion + */ + public function userArchivesConversation(string $user, string $identifier, string $action, int $statusCode, string $apiVersion): void { + $httpMethod = 'POST'; + + if ($action === 'unarchives') { + $httpMethod = 'DELETE'; + } + + $this->setCurrentUser($user); + $this->sendRequest( + $httpMethod, '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/archive', + ); + $this->assertStatusCode($this->response, $statusCode); + } + + /** + * @When /^user "([^"]*)" unarchives room "([^"]*)" with (\d+) \((v4)\)$/ + * + * @param string $user + * @param string $identifier + * @param int $statusCode + * @param string $apiVersion + */ + public function userUnarchivesConversation(string $user, string $identifier, int $statusCode, string $apiVersion): void { + $this->setCurrentUser($user); + $this->sendRequest( + 'DELETE', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/archive', + ); + $this->assertStatusCode($this->response, $statusCode); + } + /** * @param string $verb * @param string $fullUrl diff --git a/tests/php/Chat/ChatManagerTest.php b/tests/php/Chat/ChatManagerTest.php index 628b6d3ab4f..72d844199dd 100644 --- a/tests/php/Chat/ChatManagerTest.php +++ b/tests/php/Chat/ChatManagerTest.php @@ -428,6 +428,7 @@ public function testDeleteMessage(): void { 'state' => Invitation::STATE_ACCEPTED, 'unread_messages' => 0, 'last_attendee_activity' => 0, + 'archived' => 0, ]); $chat = $this->createMock(Room::class); $chat->expects($this->any()) @@ -490,6 +491,7 @@ public function testDeleteMessageFileShare(): void { 'state' => Invitation::STATE_ACCEPTED, 'unread_messages' => 0, 'last_attendee_activity' => 0, + 'archived' => 0, ]); $chat = $this->createMock(Room::class); $chat->expects($this->any()) @@ -574,6 +576,7 @@ public function testDeleteMessageFileShareNotFound(): void { 'state' => Invitation::STATE_ACCEPTED, 'unread_messages' => 0, 'last_attendee_activity' => 0, + 'archived' => 0, ]); $chat = $this->createMock(Room::class); $chat->expects($this->any()) From e5d37ded241c6ebc27abc937bcce1fc1b943f1a0 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Fri, 20 Sep 2024 11:06:06 +0200 Subject: [PATCH 2/2] fix(archive): Don't send notifications for default calls and at-all mentions Signed-off-by: Joas Schilling --- lib/Chat/Notifier.php | 17 +++++++---------- lib/Service/ParticipantService.php | 1 + 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/lib/Chat/Notifier.php b/lib/Chat/Notifier.php index 653efb57205..a48f199aed7 100644 --- a/lib/Chat/Notifier.php +++ b/lib/Chat/Notifier.php @@ -76,9 +76,8 @@ public function notifyMentionedUsers(Room $chat, IComment $comment, array $alrea $shouldFlush = $this->notificationManager->defer(); } - foreach ($usersToNotify as $mentionedUser) { - if ($this->shouldMentionedUserBeNotified($mentionedUser['id'], $comment, $chat, $mentionedUser['attendee'] ?? null)) { + if ($this->shouldMentionedUserBeNotified($mentionedUser['id'], $comment, $chat, $mentionedUser['attendee'] ?? null, $mentionedUser['reason'])) { if (!$silent) { $notification->setUser($mentionedUser['id']); if (isset($mentionedUser['reason'])) { @@ -210,7 +209,7 @@ public function notifyReplyToAuthor(Room $chat, IComment $comment, IComment $rep ]; } - if (!$this->shouldMentionedUserBeNotified($replyTo->getActorId(), $comment, $chat)) { + if (!$this->shouldMentionedUserBeNotified($replyTo->getActorId(), $comment, $chat, null, 'reply')) { return []; } @@ -565,14 +564,8 @@ protected function getDefaultGroupNotification(): int { * 2. The user must exist * 3. The user must be a participant of the room * 4. The user must not be active in the room - * - * @param string $userId - * @param IComment $comment - * @param Room $room - * @param Attendee|null $attendee - * @return bool */ - protected function shouldMentionedUserBeNotified(string $userId, IComment $comment, Room $room, ?Attendee $attendee = null): bool { + protected function shouldMentionedUserBeNotified(string $userId, IComment $comment, Room $room, ?Attendee $attendee, string $reason): bool { if ($comment->getActorType() === Attendee::ACTOR_USERS && $userId === $comment->getActorId()) { // Do not notify the user if they mentioned themselves return false; @@ -590,6 +583,10 @@ protected function shouldMentionedUserBeNotified(string $userId, IComment $comme $participant = new Participant($room, $attendee, null); } + if ($reason === 'all' && $attendee->isArchived()) { + return false; + } + if ($room->getLobbyState() !== Webinary::LOBBY_NONE && !($participant->getPermissions() & Attendee::PERMISSIONS_LOBBY_IGNORE)) { return false; diff --git a/lib/Service/ParticipantService.php b/lib/Service/ParticipantService.php index 71ba2e4f0c5..16e9db52ad4 100644 --- a/lib/Service/ParticipantService.php +++ b/lib/Service/ParticipantService.php @@ -1753,6 +1753,7 @@ public function getParticipantUserIdsForCallNotifications(Room $room): array { ->where($query->expr()->eq('a.room_id', $query->createNamedParameter($room->getId(), IQueryBuilder::PARAM_INT))) ->andWhere($query->expr()->eq('a.actor_type', $query->createNamedParameter(Attendee::ACTOR_USERS))) ->andWhere($query->expr()->eq('a.notification_calls', $query->createNamedParameter(Participant::NOTIFY_CALLS_ON))) + ->andWhere($query->expr()->eq('a.archived', $query->createNamedParameter(false, IQueryBuilder::PARAM_BOOL))) ->andWhere($query->expr()->isNull('s.in_call')); if ($room->getLobbyState() !== Webinary::LOBBY_NONE) {