mirror of
https://github.com/nextcloud/spreed.git
synced 2025-12-18 05:20:50 +01:00
Merge pull request #15053 from nextcloud/feat/noid/sensitive-conversations
feat(conversations): Add "sensitive conversations" that hide the last message
This commit is contained in:
commit
a8ef63638b
28 changed files with 794 additions and 10 deletions
|
|
@ -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.
|
||||
]]></description>
|
||||
|
||||
<version>22.0.0-dev.6</version>
|
||||
<version>22.0.0-dev.7</version>
|
||||
<licence>agpl</licence>
|
||||
|
||||
<author>Anna Larch</author>
|
||||
|
|
|
|||
|
|
@ -89,6 +89,10 @@ return [
|
|||
['name' => 'Room#markConversationAsImportant', 'url' => '/api/{apiVersion}/room/{token}/important', 'verb' => 'POST', 'requirements' => $requirementsWithToken],
|
||||
/** @see \OCA\Talk\Controller\RoomController::markConversationAsUnimportant() */
|
||||
['name' => 'Room#markConversationAsUnimportant', 'url' => '/api/{apiVersion}/room/{token}/important', 'verb' => 'DELETE', 'requirements' => $requirementsWithToken],
|
||||
/** @see \OCA\Talk\Controller\RoomController::markConversationAsSensitive() */
|
||||
['name' => 'Room#markConversationAsSensitive', 'url' => '/api/{apiVersion}/room/{token}/sensitive', 'verb' => 'POST', 'requirements' => $requirementsWithToken],
|
||||
/** @see \OCA\Talk\Controller\RoomController::markConversationAsInsensitive() */
|
||||
['name' => 'Room#markConversationAsInsensitive', 'url' => '/api/{apiVersion}/room/{token}/sensitive', 'verb' => 'DELETE', 'requirements' => $requirementsWithToken],
|
||||
/** @see \OCA\Talk\Controller\RoomController::verifyDialInPin() */
|
||||
['name' => 'Room#verifyDialInPin', 'url' => '/api/{apiVersion}/room/{token}/pin/{pin}', 'verb' => 'GET', 'requirements' => array_merge($requirementsWithToken, [
|
||||
'pin' => '\d{7,32}',
|
||||
|
|
|
|||
|
|
@ -191,3 +191,4 @@
|
|||
* `dashboard-event-rooms` (local) - Whether Talk APIs offer functionality for Dashboard requests
|
||||
* `mutual-calendar-events` (local) - Whether Talk APIs offer mutual calendar events for 1:1 rooms
|
||||
* `upcoming-reminders` (local) - Whether the API to list upcoming reminders exists
|
||||
* `sensitive-conversations` (local) - Whether sensitive conversations are supported
|
||||
|
|
|
|||
|
|
@ -121,6 +121,7 @@ class Capabilities implements IPublicCapability {
|
|||
'dashboard-event-rooms',
|
||||
'mutual-calendar-events',
|
||||
'upcoming-reminders',
|
||||
'sensitive-conversations',
|
||||
];
|
||||
|
||||
public const CONDITIONAL_FEATURES = [
|
||||
|
|
|
|||
|
|
@ -1757,6 +1757,40 @@ class RoomController extends AEnvironmentAwareOCSController {
|
|||
return new DataResponse($this->formatRoom($this->room, $this->participant));
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark a conversation as sensitive (no last message is visible / no push preview is shown)
|
||||
*
|
||||
* Required capability: `sensitive-conversations`
|
||||
*
|
||||
* @return DataResponse<Http::STATUS_OK, TalkRoom, array{}>
|
||||
*
|
||||
* 200: Conversation was marked as sensitive
|
||||
*/
|
||||
#[NoAdminRequired]
|
||||
#[FederationSupported]
|
||||
#[RequireLoggedInParticipant]
|
||||
public function markConversationAsSensitive(): DataResponse {
|
||||
$this->participantService->markConversationAsSensitive($this->participant);
|
||||
return new DataResponse($this->formatRoom($this->room, $this->participant));
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark a conversation as insensitive (last message is visible / push preview is shown)
|
||||
*
|
||||
* Required capability: `sensitive-conversations`
|
||||
*
|
||||
* @return DataResponse<Http::STATUS_OK, TalkRoom, array{}>
|
||||
*
|
||||
* 200: Conversation was marked as insensitive
|
||||
*/
|
||||
#[NoAdminRequired]
|
||||
#[FederationSupported]
|
||||
#[RequireLoggedInParticipant]
|
||||
public function markConversationAsInsensitive(): DataResponse {
|
||||
$this->participantService->markConversationAsInsensitive($this->participant);
|
||||
return new DataResponse($this->formatRoom($this->room, $this->participant));
|
||||
}
|
||||
|
||||
/**
|
||||
* Join a room
|
||||
*
|
||||
|
|
|
|||
|
|
@ -265,9 +265,12 @@ class TalkWidget implements IAPIWidget, IIconWidget, IButtonWidget, IOptionWidge
|
|||
|
||||
protected function prepareRoom(Room $room, string $userId): WidgetItem {
|
||||
$participant = $this->participantService->getParticipant($room, $userId);
|
||||
$attendee = $participant->getAttendee();
|
||||
$subtitle = '';
|
||||
|
||||
if ($room->getLastMessageId() && $room->isFederatedConversation()) {
|
||||
if ($attendee->isSensitive()) {
|
||||
// Don't leak sensitive last messages on dashboard
|
||||
} elseif ($room->getLastMessageId() && $room->isFederatedConversation()) {
|
||||
try {
|
||||
$cachedMessage = $this->pcmService->findByRemote(
|
||||
$room->getRemoteServer(),
|
||||
|
|
@ -285,7 +288,6 @@ class TalkWidget implements IAPIWidget, IIconWidget, IButtonWidget, IOptionWidge
|
|||
$subtitle = $this->getSubtitleFromMessage($message);
|
||||
}
|
||||
|
||||
$attendee = $participant->getAttendee();
|
||||
if ($room->getCallFlag() !== Participant::FLAG_DISCONNECTED) {
|
||||
$subtitle = $this->l10n->t('Call in progress');
|
||||
} elseif (($room->isFederatedConversation() && $attendee->getLastMentionMessage())
|
||||
|
|
|
|||
41
lib/Migration/Version21001Date20250509153510.php
Normal file
41
lib/Migration/Version21001Date20250509153510.php
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\Talk\Migration;
|
||||
|
||||
use Closure;
|
||||
use OCP\DB\ISchemaWrapper;
|
||||
use OCP\DB\Types;
|
||||
use OCP\Migration\IOutput;
|
||||
use OCP\Migration\SimpleMigrationStep;
|
||||
|
||||
class Version21001Date20250509153510 extends SimpleMigrationStep {
|
||||
|
||||
/**
|
||||
* @param IOutput $output
|
||||
* @param Closure(): ISchemaWrapper $schemaClosure
|
||||
* @param array $options
|
||||
* @return null|ISchemaWrapper
|
||||
*/
|
||||
#[\Override]
|
||||
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
|
||||
/** @var ISchemaWrapper $schema */
|
||||
$schema = $schemaClosure();
|
||||
|
||||
$table = $schema->getTable('talk_attendees');
|
||||
if (!$table->hasColumn('sensitive')) {
|
||||
$table->addColumn('sensitive', Types::BOOLEAN, [
|
||||
'default' => 0,
|
||||
'notnull' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
return $schema;
|
||||
}
|
||||
}
|
||||
|
|
@ -44,6 +44,8 @@ use OCP\DB\Types;
|
|||
* @method bool isArchived()
|
||||
* @method void setImportant(bool $important)
|
||||
* @method bool isImportant()
|
||||
* @method void setSensitive(bool $important)
|
||||
* @method bool isSensitive()
|
||||
* @internal
|
||||
* @method int getPermissions()
|
||||
* @method void setAccessToken(string $accessToken)
|
||||
|
|
@ -116,6 +118,7 @@ class Attendee extends Entity {
|
|||
protected int $notificationCalls = 0;
|
||||
protected bool $archived = false;
|
||||
protected bool $important = false;
|
||||
protected bool $sensitive = false;
|
||||
protected int $lastJoinedCall = 0;
|
||||
protected int $lastReadMessage = 0;
|
||||
protected int $lastMentionMessage = 0;
|
||||
|
|
@ -141,6 +144,7 @@ class Attendee extends Entity {
|
|||
$this->addType('favorite', Types::BOOLEAN);
|
||||
$this->addType('archived', Types::BOOLEAN);
|
||||
$this->addType('important', Types::BOOLEAN);
|
||||
$this->addType('sensitive', Types::BOOLEAN);
|
||||
$this->addType('notificationLevel', Types::INTEGER);
|
||||
$this->addType('notificationCalls', Types::INTEGER);
|
||||
$this->addType('lastJoinedCall', Types::INTEGER);
|
||||
|
|
|
|||
|
|
@ -310,6 +310,7 @@ class AttendeeMapper extends QBMapper {
|
|||
'last_attendee_activity' => (int)$row['last_attendee_activity'],
|
||||
'archived' => (bool)$row['archived'],
|
||||
'important' => (bool)$row['important'],
|
||||
'sensitive' => (bool)$row['sensitive'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@ class SelectHelper {
|
|||
->addSelect($alias . 'last_attendee_activity')
|
||||
->addSelect($alias . 'archived')
|
||||
->addSelect($alias . 'important')
|
||||
->addSelect($alias . 'sensitive')
|
||||
->selectAlias($alias . 'id', 'a_id');
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -626,7 +626,7 @@ class Notifier implements INotifier {
|
|||
}
|
||||
|
||||
$parsedMessage = str_replace($placeholders, $replacements, $message->getMessage());
|
||||
if (!$this->notificationManager->isPreparingPushNotification()) {
|
||||
if (!$this->notificationManager->isPreparingPushNotification() && !$participant->getAttendee()->isSensitive()) {
|
||||
$notification->setParsedMessage($parsedMessage);
|
||||
$notification->setRichMessage($message->getMessage(), $message->getMessageParameters());
|
||||
|
||||
|
|
@ -639,7 +639,44 @@ class Notifier implements INotifier {
|
|||
'call' => $richSubjectCall,
|
||||
];
|
||||
|
||||
if ($this->notificationManager->isPreparingPushNotification()) {
|
||||
if ($participant->getAttendee()->isSensitive()) {
|
||||
// Prevent message preview and conversation name in sensitive conversations
|
||||
|
||||
if ($this->notificationManager->isPreparingPushNotification()) {
|
||||
$translatedPrivateConversation = $l->t('Private conversation');
|
||||
|
||||
if ($notification->getSubject() === 'reaction') {
|
||||
// TRANSLATORS Someone reacted in a private conversation
|
||||
$subject = $translatedPrivateConversation . "\n" . $l->t('Someone reacted');
|
||||
} elseif ($notification->getSubject() === 'chat') {
|
||||
// TRANSLATORS You received a new message in a private conversation
|
||||
$subject = $translatedPrivateConversation . "\n" . $l->t('New message');
|
||||
} elseif ($notification->getSubject() === 'reminder') {
|
||||
// TRANSLATORS Reminder for a message in a private conversation
|
||||
$subject = $translatedPrivateConversation . "\n" . $l->t('Reminder');
|
||||
} elseif (str_starts_with($notification->getSubject(), 'mention_')) {
|
||||
// TRANSLATORS Someone mentioned you in a private conversation
|
||||
$subject = $translatedPrivateConversation . "\n" . $l->t('Someone mentioned you');
|
||||
} else {
|
||||
// TRANSLATORS There's a notification in a private conversation
|
||||
$subject = $translatedPrivateConversation . "\n" . $l->t('Notification');
|
||||
}
|
||||
} else {
|
||||
if ($notification->getSubject() === 'reaction') {
|
||||
$subject = $l->t('Someone reacted in a private conversation');
|
||||
} elseif ($notification->getSubject() === 'chat') {
|
||||
$subject = $l->t('You received a message in a private conversation');
|
||||
} elseif ($notification->getSubject() === 'reminder') {
|
||||
$subject = $l->t('Reminder in a private conversation');
|
||||
} elseif (str_starts_with($notification->getSubject(), 'mention_')) {
|
||||
$subject = $l->t('Someone mentioned you in a private conversation');
|
||||
} else {
|
||||
$subject = $l->t('Notification in a private conversation');
|
||||
}
|
||||
}
|
||||
|
||||
$richSubjectParameters = [];
|
||||
} elseif ($this->notificationManager->isPreparingPushNotification()) {
|
||||
$shortenMessage = $this->shortenJsonEncodedMultibyteSave($parsedMessage, 100);
|
||||
if ($shortenMessage !== $parsedMessage) {
|
||||
$shortenMessage .= '…';
|
||||
|
|
|
|||
|
|
@ -360,6 +360,8 @@ namespace OCA\Talk;
|
|||
* isArchived: bool,
|
||||
* // Required capability: `important-conversations`
|
||||
* isImportant: bool,
|
||||
* // Required capability: `sensitive-conversations`
|
||||
* isSensitive: bool,
|
||||
* }
|
||||
*
|
||||
* @psalm-type TalkDashboardEventAttachment = array{
|
||||
|
|
|
|||
|
|
@ -344,6 +344,26 @@ class ParticipantService {
|
|||
$this->attendeeMapper->update($attendee);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Participant $participant
|
||||
*/
|
||||
public function markConversationAsSensitive(Participant $participant): void {
|
||||
$attendee = $participant->getAttendee();
|
||||
$attendee->setSensitive(true);
|
||||
$attendee->setLastAttendeeActivity($this->timeFactory->getTime());
|
||||
$this->attendeeMapper->update($attendee);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Participant $participant
|
||||
*/
|
||||
public function markConversationAsInsensitive(Participant $participant): void {
|
||||
$attendee = $participant->getAttendee();
|
||||
$attendee->setSensitive(false);
|
||||
$attendee->setLastAttendeeActivity($this->timeFactory->getTime());
|
||||
$this->attendeeMapper->update($attendee);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RoomService $roomService
|
||||
* @param Room $room
|
||||
|
|
|
|||
|
|
@ -146,6 +146,7 @@ class RoomFormatter {
|
|||
'mentionPermissions' => Room::MENTION_PERMISSIONS_EVERYONE,
|
||||
'isArchived' => false,
|
||||
'isImportant' => false,
|
||||
'isSensitive' => false,
|
||||
];
|
||||
|
||||
if ($room->isFederatedConversation()) {
|
||||
|
|
@ -231,6 +232,7 @@ class RoomFormatter {
|
|||
'mentionPermissions' => $room->getMentionPermissions(),
|
||||
'isArchived' => $attendee->isArchived(),
|
||||
'isImportant' => $attendee->isImportant(),
|
||||
'isSensitive' => $attendee->isSensitive(),
|
||||
]);
|
||||
|
||||
if ($room->isFederatedConversation()) {
|
||||
|
|
@ -391,6 +393,7 @@ class RoomFormatter {
|
|||
}
|
||||
}
|
||||
|
||||
$skipLastMessage = $skipLastMessage || $attendee->isSensitive();
|
||||
$lastMessage = $skipLastMessage ? null : $room->getLastMessage();
|
||||
if (!$room->isFederatedConversation() && $lastMessage instanceof IComment) {
|
||||
$lastMessageData = $this->formatLastMessage(
|
||||
|
|
|
|||
|
|
@ -616,7 +616,8 @@
|
|||
"unreadMentionDirect",
|
||||
"unreadMessages",
|
||||
"isArchived",
|
||||
"isImportant"
|
||||
"isImportant",
|
||||
"isSensitive"
|
||||
],
|
||||
"properties": {
|
||||
"actorId": {
|
||||
|
|
@ -901,6 +902,10 @@
|
|||
"isImportant": {
|
||||
"type": "boolean",
|
||||
"description": "Required capability: `important-conversations`"
|
||||
},
|
||||
"isSensitive": {
|
||||
"type": "boolean",
|
||||
"description": "Required capability: `sensitive-conversations`"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -670,7 +670,8 @@
|
|||
"unreadMentionDirect",
|
||||
"unreadMessages",
|
||||
"isArchived",
|
||||
"isImportant"
|
||||
"isImportant",
|
||||
"isSensitive"
|
||||
],
|
||||
"properties": {
|
||||
"actorId": {
|
||||
|
|
@ -955,6 +956,10 @@
|
|||
"isImportant": {
|
||||
"type": "boolean",
|
||||
"description": "Required capability: `important-conversations`"
|
||||
},
|
||||
"isSensitive": {
|
||||
"type": "boolean",
|
||||
"description": "Required capability: `sensitive-conversations`"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1465,7 +1465,8 @@
|
|||
"unreadMentionDirect",
|
||||
"unreadMessages",
|
||||
"isArchived",
|
||||
"isImportant"
|
||||
"isImportant",
|
||||
"isSensitive"
|
||||
],
|
||||
"properties": {
|
||||
"actorId": {
|
||||
|
|
@ -1750,6 +1751,10 @@
|
|||
"isImportant": {
|
||||
"type": "boolean",
|
||||
"description": "Required capability: `important-conversations`"
|
||||
},
|
||||
"isSensitive": {
|
||||
"type": "boolean",
|
||||
"description": "Required capability: `sensitive-conversations`"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -17517,6 +17522,170 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/ocs/v2.php/apps/spreed/api/{apiVersion}/room/{token}/sensitive": {
|
||||
"post": {
|
||||
"operationId": "room-mark-conversation-as-sensitive",
|
||||
"summary": "Mark a conversation as sensitive (no last message is visible / no push preview is shown)",
|
||||
"description": "Required capability: `sensitive-conversations`",
|
||||
"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 marked as sensitive",
|
||||
"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-mark-conversation-as-insensitive",
|
||||
"summary": "Mark a conversation as insensitive (last message is visible / push preview is shown)",
|
||||
"description": "Required capability: `sensitive-conversations`",
|
||||
"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 marked as insensitive",
|
||||
"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}/room/{token}/notify": {
|
||||
"post": {
|
||||
"operationId": "room-set-notification-level",
|
||||
|
|
|
|||
171
openapi.json
171
openapi.json
|
|
@ -1370,7 +1370,8 @@
|
|||
"unreadMentionDirect",
|
||||
"unreadMessages",
|
||||
"isArchived",
|
||||
"isImportant"
|
||||
"isImportant",
|
||||
"isSensitive"
|
||||
],
|
||||
"properties": {
|
||||
"actorId": {
|
||||
|
|
@ -1655,6 +1656,10 @@
|
|||
"isImportant": {
|
||||
"type": "boolean",
|
||||
"description": "Required capability: `important-conversations`"
|
||||
},
|
||||
"isSensitive": {
|
||||
"type": "boolean",
|
||||
"description": "Required capability: `sensitive-conversations`"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -17422,6 +17427,170 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/ocs/v2.php/apps/spreed/api/{apiVersion}/room/{token}/sensitive": {
|
||||
"post": {
|
||||
"operationId": "room-mark-conversation-as-sensitive",
|
||||
"summary": "Mark a conversation as sensitive (no last message is visible / no push preview is shown)",
|
||||
"description": "Required capability: `sensitive-conversations`",
|
||||
"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 marked as sensitive",
|
||||
"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-mark-conversation-as-insensitive",
|
||||
"summary": "Mark a conversation as insensitive (last message is visible / push preview is shown)",
|
||||
"description": "Required capability: `sensitive-conversations`",
|
||||
"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 marked as insensitive",
|
||||
"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}/room/{token}/notify": {
|
||||
"post": {
|
||||
"operationId": "room-set-notification-level",
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ describe('Conversation.vue', () => {
|
|||
displayName: 'conversation one',
|
||||
isFavorite: false,
|
||||
isArchived: false,
|
||||
isSensitive: false,
|
||||
lastMessage: {
|
||||
actorId: 'user-id-alice',
|
||||
actorDisplayName: 'Alice Wonderland',
|
||||
|
|
@ -199,6 +200,22 @@ describe('Conversation.vue', () => {
|
|||
const wrapper = testConversationLabel(item, /^Alice:\s+filename.jpg$/)
|
||||
expect(wrapper.findComponent({ name: 'FileIcon' }).exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
test('hides subname for sensitive conversations', () => {
|
||||
item.isSensitive = true
|
||||
|
||||
const wrapper = shallowMount(Conversation, {
|
||||
localVue,
|
||||
store,
|
||||
propsData: {
|
||||
isSearchResult: false,
|
||||
item,
|
||||
},
|
||||
})
|
||||
|
||||
const el = wrapper.find('.conversation__subname')
|
||||
expect(el.exists()).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('unread messages counter', () => {
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@
|
|||
</template>
|
||||
<span class="text"> {{ item.displayName }} </span>
|
||||
</template>
|
||||
<template v-if="!compact" #subname>
|
||||
<template v-if="!compact && !item.isSensitive" #subname>
|
||||
<span class="conversation__subname" :title="conversationInformation.title">
|
||||
<span v-if="conversationInformation.actor"
|
||||
class="conversation__subname-actor">
|
||||
|
|
@ -322,6 +322,7 @@ export default {
|
|||
canDeleteConversation: false,
|
||||
canLeaveConversation: false,
|
||||
hasCall: false,
|
||||
isSensitive: false,
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -494,6 +494,8 @@ export type components = {
|
|||
isArchived: boolean;
|
||||
/** @description Required capability: `important-conversations` */
|
||||
isImportant: boolean;
|
||||
/** @description Required capability: `sensitive-conversations` */
|
||||
isSensitive: boolean;
|
||||
};
|
||||
RoomLastMessage: components["schemas"]["ChatMessage"] | components["schemas"]["ChatProxyMessage"];
|
||||
};
|
||||
|
|
|
|||
|
|
@ -521,6 +521,8 @@ export type components = {
|
|||
isArchived: boolean;
|
||||
/** @description Required capability: `important-conversations` */
|
||||
isImportant: boolean;
|
||||
/** @description Required capability: `sensitive-conversations` */
|
||||
isSensitive: boolean;
|
||||
};
|
||||
RoomLastMessage: components["schemas"]["ChatMessage"] | components["schemas"]["ChatProxyMessage"];
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1295,6 +1295,30 @@ export type paths = {
|
|||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/ocs/v2.php/apps/spreed/api/{apiVersion}/room/{token}/sensitive": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/**
|
||||
* Mark a conversation as sensitive (no last message is visible / no push preview is shown)
|
||||
* @description Required capability: `sensitive-conversations`
|
||||
*/
|
||||
post: operations["room-mark-conversation-as-sensitive"];
|
||||
/**
|
||||
* Mark a conversation as insensitive (last message is visible / push preview is shown)
|
||||
* @description Required capability: `sensitive-conversations`
|
||||
*/
|
||||
delete: operations["room-mark-conversation-as-insensitive"];
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/ocs/v2.php/apps/spreed/api/{apiVersion}/room/{token}/notify": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
|
|
@ -2685,6 +2709,8 @@ export type components = {
|
|||
isArchived: boolean;
|
||||
/** @description Required capability: `important-conversations` */
|
||||
isImportant: boolean;
|
||||
/** @description Required capability: `sensitive-conversations` */
|
||||
isSensitive: boolean;
|
||||
};
|
||||
RoomLastMessage: components["schemas"]["ChatMessage"] | components["schemas"]["ChatProxyMessage"];
|
||||
RoomWithInvalidInvitations: components["schemas"]["Room"] & {
|
||||
|
|
@ -8882,6 +8908,68 @@ export interface operations {
|
|||
};
|
||||
};
|
||||
};
|
||||
"room-mark-conversation-as-sensitive": {
|
||||
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 marked as sensitive */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": {
|
||||
ocs: {
|
||||
meta: components["schemas"]["OCSMeta"];
|
||||
data: components["schemas"]["Room"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
"room-mark-conversation-as-insensitive": {
|
||||
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 marked as insensitive */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": {
|
||||
ocs: {
|
||||
meta: components["schemas"]["OCSMeta"];
|
||||
data: components["schemas"]["Room"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
"room-set-notification-level": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
|
|
|
|||
|
|
@ -1295,6 +1295,30 @@ export type paths = {
|
|||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/ocs/v2.php/apps/spreed/api/{apiVersion}/room/{token}/sensitive": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/**
|
||||
* Mark a conversation as sensitive (no last message is visible / no push preview is shown)
|
||||
* @description Required capability: `sensitive-conversations`
|
||||
*/
|
||||
post: operations["room-mark-conversation-as-sensitive"];
|
||||
/**
|
||||
* Mark a conversation as insensitive (last message is visible / push preview is shown)
|
||||
* @description Required capability: `sensitive-conversations`
|
||||
*/
|
||||
delete: operations["room-mark-conversation-as-insensitive"];
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/ocs/v2.php/apps/spreed/api/{apiVersion}/room/{token}/notify": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
|
|
@ -2147,6 +2171,8 @@ export type components = {
|
|||
isArchived: boolean;
|
||||
/** @description Required capability: `important-conversations` */
|
||||
isImportant: boolean;
|
||||
/** @description Required capability: `sensitive-conversations` */
|
||||
isSensitive: boolean;
|
||||
};
|
||||
RoomLastMessage: components["schemas"]["ChatMessage"] | components["schemas"]["ChatProxyMessage"];
|
||||
RoomWithInvalidInvitations: components["schemas"]["Room"] & {
|
||||
|
|
@ -8344,6 +8370,68 @@ export interface operations {
|
|||
};
|
||||
};
|
||||
};
|
||||
"room-mark-conversation-as-sensitive": {
|
||||
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 marked as sensitive */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": {
|
||||
ocs: {
|
||||
meta: components["schemas"]["OCSMeta"];
|
||||
data: components["schemas"]["Room"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
"room-mark-conversation-as-insensitive": {
|
||||
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 marked as insensitive */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": {
|
||||
ocs: {
|
||||
meta: components["schemas"]["OCSMeta"];
|
||||
data: components["schemas"]["Room"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
"room-set-notification-level": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
|
|
|
|||
|
|
@ -490,6 +490,9 @@ class FeatureContext implements Context, SnippetAcceptingContext {
|
|||
if (isset($expectedRoom['isArchived'])) {
|
||||
$data['isArchived'] = (int)$room['isArchived'];
|
||||
}
|
||||
if (isset($expectedRoom['isSensitive'])) {
|
||||
$data['isSensitive'] = (int)$room['isSensitive'];
|
||||
}
|
||||
if (isset($expectedRoom['participantType'])) {
|
||||
$data['participantType'] = (string)$room['participantType'];
|
||||
}
|
||||
|
|
@ -4372,6 +4375,21 @@ class FeatureContext implements Context, SnippetAcceptingContext {
|
|||
$this->assertStatusCode($this->response, $statusCode);
|
||||
}
|
||||
|
||||
#[When('/^user "([^"]*)" marks room "([^"]*)" as (sensitive|insensitive) with (\d+) \((v4)\)$/')]
|
||||
public function userMarksConversationSensitive(string $user, string $identifier, string $action, int $statusCode, string $apiVersion): void {
|
||||
$httpMethod = 'POST';
|
||||
|
||||
if ($action === 'insensitive') {
|
||||
$httpMethod = 'DELETE';
|
||||
}
|
||||
|
||||
$this->setCurrentUser($user);
|
||||
$this->sendRequest(
|
||||
$httpMethod, '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/sensitive',
|
||||
);
|
||||
$this->assertStatusCode($this->response, $statusCode);
|
||||
}
|
||||
|
||||
public function sendRequestFullUrl(string $verb, string $fullUrl, TableNode|array|string|null $body = null, array $headers = [], array $options = []): void {
|
||||
$client = new Client();
|
||||
$options = array_merge($options, ['cookies' => $this->getUserCookieJar($this->currentUser)]);
|
||||
|
|
|
|||
52
tests/integration/features/conversation-5/sensitive.feature
Normal file
52
tests/integration/features/conversation-5/sensitive.feature
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
Feature: conversation-5/sensitive
|
||||
Background:
|
||||
Given user "participant1" exists
|
||||
Given user "participant2" exists
|
||||
|
||||
Scenario: Mark as (in-)sensitive
|
||||
Given user "participant1" creates room "group room" (v4)
|
||||
| roomType | 3 |
|
||||
| roomName | room |
|
||||
When user "participant1" creates room "one-to-one room" (v4)
|
||||
| roomType | 1 |
|
||||
| invite | participant2 |
|
||||
And user "participant1" sends message "Message 1" to room "group room" with 201
|
||||
And user "participant1" sends message "Message 2" to room "one-to-one room" with 201
|
||||
And user "participant1" is participant of the following unordered rooms (v4)
|
||||
| id | name | lastMessage | isSensitive |
|
||||
| group room | room | Message 1 | 0 |
|
||||
| one-to-one room | participant2 | Message 2 | 0 |
|
||||
And user "participant1" marks room "one-to-one room" as sensitive with 200 (v4)
|
||||
And user "participant1" marks room "group room" as sensitive with 200 (v4)
|
||||
And user "participant1" is participant of the following unordered rooms (v4)
|
||||
| id | name | lastMessage | isSensitive |
|
||||
| group room | room | UNSET | 1 |
|
||||
| one-to-one room | participant2 | UNSET | 1 |
|
||||
And user "participant1" marks room "one-to-one room" as insensitive with 200 (v4)
|
||||
And user "participant1" marks room "group room" as insensitive with 200 (v4)
|
||||
And user "participant1" is participant of the following unordered rooms (v4)
|
||||
| id | name | lastMessage | isSensitive |
|
||||
| group room | room | Message 1 | 0 |
|
||||
| one-to-one room | participant2 | Message 2 | 0 |
|
||||
|
||||
Scenario: Message preview hidden in sensitive rooms for notifications
|
||||
When user "participant1" creates room "one-to-one room" (v4)
|
||||
| roomType | 1 |
|
||||
| invite | participant2 |
|
||||
And user "participant2" creates room "one-to-one room" with 200 (v4)
|
||||
| roomType | 1 |
|
||||
| invite | participant1 |
|
||||
And user "participant2" marks room "one-to-one room" as sensitive with 200 (v4)
|
||||
And user "participant1" sends message "Secret message" to room "one-to-one room" with 201
|
||||
And user "participant1" sends message "Secret mention for @participant2" to room "one-to-one room" with 201
|
||||
Then user "participant2" has the following notifications
|
||||
| app | object_type | object_id | subject | message |
|
||||
| spreed | chat | one-to-one room | Someone mentioned you in a private conversation | |
|
||||
| spreed | chat | one-to-one room | You received a message in a private conversation | |
|
||||
When user "participant2" marks room "one-to-one room" as insensitive with 200 (v4)
|
||||
And user "participant1" sends message "Nonsecret message" to room "one-to-one room" with 201
|
||||
Then user "participant2" has the following notifications
|
||||
| app | object_type | object_id | subject | message |
|
||||
| spreed | chat | one-to-one room/Nonsecret message | participant1-displayname sent you a private message | Nonsecret message |
|
||||
| spreed | chat | one-to-one room/Secret mention for @participant2 | participant1-displayname mentioned you in a private conversation | Secret mention for @participant2-displayname |
|
||||
| spreed | chat | one-to-one room/Secret message | participant1-displayname sent you a private message | Secret message |
|
||||
|
|
@ -85,6 +85,20 @@ Feature: integration/dashboard-server
|
|||
And user "participant1" unarchives room "one-to-one room" with 200 (v4)
|
||||
And user "participant1" unarchives room "group room" with 200 (v4)
|
||||
And user "participant1" unarchives room "call room" with 200 (v4)
|
||||
And user "participant1" marks room "one-to-one room" as sensitive with 200 (v4)
|
||||
Then user "participant1" sees the following entries for dashboard widgets "spreed" (v1)
|
||||
| title | subtitle | link | iconUrl | sinceId | overlayIconUrl |
|
||||
| call room | Call in progress | call room | {$BASE_URL}ocs/v2.php/apps/spreed/api/v1/room/{token}/avatar{version} | | |
|
||||
| lobby room with bypass | You were mentioned | lobby room with bypass | {$BASE_URL}ocs/v2.php/apps/spreed/api/v1/room/{token}/avatar{version} | | |
|
||||
| group room | You were mentioned | group room | {$BASE_URL}ocs/v2.php/apps/spreed/api/v1/room/{token}/avatar{version} | | |
|
||||
| participant2-displayname | | one-to-one room | {$BASE_URL}ocs/v2.php/apps/spreed/api/v1/room/{token}/avatar{version} | | |
|
||||
Then user "participant1" sees the following entries for dashboard widgets "spreed" (v2)
|
||||
| title | subtitle | link | iconUrl | sinceId | overlayIconUrl |
|
||||
| call room | Call in progress | call room | {$BASE_URL}ocs/v2.php/apps/spreed/api/v1/room/{token}/avatar{version} | | |
|
||||
| lobby room with bypass | You were mentioned | lobby room with bypass | {$BASE_URL}ocs/v2.php/apps/spreed/api/v1/room/{token}/avatar{version} | | |
|
||||
| group room | You were mentioned | group room | {$BASE_URL}ocs/v2.php/apps/spreed/api/v1/room/{token}/avatar{version} | | |
|
||||
| participant2-displayname | | one-to-one room | {$BASE_URL}ocs/v2.php/apps/spreed/api/v1/room/{token}/avatar{version} | | |
|
||||
And user "participant1" marks room "one-to-one room" as insensitive with 200 (v4)
|
||||
And user "participant2" set the message expiration to 3 of room "one-to-one room" with 200 (v4)
|
||||
And user "participant2" sends message "Message 3" to room "one-to-one room" with 201
|
||||
And user "participant2" set the message expiration to 3 of room "group room" with 200 (v4)
|
||||
|
|
|
|||
|
|
@ -430,6 +430,7 @@ class ChatManagerTest extends TestCase {
|
|||
'last_attendee_activity' => 0,
|
||||
'archived' => 0,
|
||||
'important' => 0,
|
||||
'sensitive' => 0,
|
||||
]);
|
||||
$chat = $this->createMock(Room::class);
|
||||
$chat->expects($this->any())
|
||||
|
|
@ -494,6 +495,7 @@ class ChatManagerTest extends TestCase {
|
|||
'last_attendee_activity' => 0,
|
||||
'archived' => 0,
|
||||
'important' => 0,
|
||||
'sensitive' => 0,
|
||||
]);
|
||||
$chat = $this->createMock(Room::class);
|
||||
$chat->expects($this->any())
|
||||
|
|
@ -580,6 +582,7 @@ class ChatManagerTest extends TestCase {
|
|||
'last_attendee_activity' => 0,
|
||||
'archived' => 0,
|
||||
'important' => 0,
|
||||
'sensitive' => 0,
|
||||
]);
|
||||
$chat = $this->createMock(Room::class);
|
||||
$chat->expects($this->any())
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue