diff options
-rw-r--r-- | appinfo/routes.php | 8 | ||||
-rw-r--r-- | docs/conversation.md | 57 | ||||
-rw-r--r-- | docs/events.md | 5 | ||||
-rw-r--r-- | lib/Controller/RoomController.php | 71 | ||||
-rw-r--r-- | lib/Manager.php | 2 | ||||
-rw-r--r-- | src/components/LeftSidebar/ConversationsList/Conversation.vue | 6 | ||||
-rw-r--r-- | src/components/LeftSidebar/LeftSidebar.vue | 30 | ||||
-rw-r--r-- | src/services/conversationsService.js | 24 |
8 files changed, 162 insertions, 41 deletions
diff --git a/appinfo/routes.php b/appinfo/routes.php index f702840f1..1c6aed1ad 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -184,6 +184,14 @@ return [ ], ], [ + 'name' => 'Room#getListedRooms', + 'url' => '/api/{apiVersion}/listed-room', + 'verb' => 'GET', + 'requirements' => [ + 'apiVersion' => 'v3', + ], + ], + [ 'name' => 'Room#createRoom', 'url' => '/api/{apiVersion}/room', 'verb' => 'POST', diff --git a/docs/conversation.md b/docs/conversation.md index 10899e69b..1c57556b6 100644 --- a/docs/conversation.md +++ b/docs/conversation.md @@ -4,6 +4,63 @@ * Base endpoint for API v2 is: `/ocs/v2.php/apps/spreed/api/v2` * Base endpoint for API v3 is: `/ocs/v2.php/apps/spreed/api/v3` +## Get listed conversations + +* Method: `GET` +* Endpoint: `/listed-room` + +* Response: + - Status code: + + `200 OK` + + `401 Unauthorized` when the user is not logged in + + - Header: + + field | type | Description + ------|------|------------ + `searchTerm` | string | search term + + - Data: + Array of conversations, each conversation has at least: + + field | type | API | Description + ------|------|-----|------------ + `token` | string | * | Token identifier of the conversation which is used for further interaction + `type` | int | * | See list of conversation types in the [constants list](constants.md#Conversation-types) + `name` | string | * | Name of the conversation (can also be empty) + `displayName` | string | * | `name` if non empty, otherwise it falls back to a list of participants + `participantType` | int | * | Permissions level of the current user + `attendeeId` | int | v3 | Unique attendee id + `attendeePin` | string | v3 | Unique dial-in authentication code for this user, when the conversation has SIP enabled (see `sipEnabled` attribute) + `actorType` | string | v3 | Currently known `users|guests|emails|groups` + `actorId` | string | v3 | The unique identifier for the given actor type + `participantInCall` | bool | π΄ v1 | Flag if the current user is in the call (deprecated, use `participantFlags` instead) + `participantFlags` | int | * | Flags of the current user (only available with `in-call-flags` capability) + `readOnly` | int | * | Read-only state for the current user (only available with `read-only-rooms` capability) + `listable` | int | * | Listable scope for the room (only available with `listable-rooms` capability) + `count` | int | π΄ v1 | **Deprecated:** ~~Number of active users~~ - always returns `0` + `numGuests` | int | π΄ v1 | Number of active guests + `lastPing` | int | * | Timestamp of the last ping of the current user (should be used for sorting) + `sessionId` | string | * | `'0'` if not connected, otherwise a 512 character long string + `hasPassword` | bool | * | Flag if the conversation has a password + `hasCall` | bool | * | Flag if the conversation has an active call + `canStartCall` | bool | * | Flag if the user can start a new call in this conversation (joining is always possible) (only available with `start-call-flag` capability) + `canDeleteConversation` | bool | π v2 | Flag if the user can delete the conversation for everyone (not possible without moderator permissions or in one-to-one conversations) + `canLeaveConversation` | bool | π v2 | Flag if the user can leave the conversation (not possible for the last user with moderator permissions) + `lastActivity` | int | * | Timestamp of the last activity in the conversation, in seconds and UTC time zone + `isFavorite` | bool | * | Flag if the conversation is favorited by the user + `notificationLevel` | int | * | The notification level for the user (one of `Participant::NOTIFY_*` (1-3)) + `lobbyState` | int | * | Webinary lobby restriction (0-1), if the participant is a moderator they can always join the conversation (only available with `webinary-lobby` capability) + `lobbyTimer` | int | * | Timestamp when the lobby will be automatically disabled (only available with `webinary-lobby` capability) + `sipEnabled` | int | v3 | SIP enable status (0-1) + `canEnableSIP` | int | v3 | Whether the given user can enable SIP for this conversation. Note that when the token is not-numeric only, SIP can not be enabled even if the user is permitted and a moderator of the conversation + `unreadMessages` | int | * | Number of unread chat messages in the conversation (only available with `chat-v2` capability) + `unreadMention` | bool | * | Flag if the user was mentioned since their last visit + `lastReadMessage` | int | * | ID of the last read message in a room (only available with `chat-read-marker` capability) + `lastMessage` | message | * | Last message in a conversation if available, otherwise empty + `objectType` | string | * | The type of object that the conversation is associated with; "share:password" if the conversation is used to request a password for a share, otherwise empty + `objectId` | string | * | Share token if "objectType" is "share:password", otherwise empty + ## Get userΒ΄s conversations * Method: `GET` diff --git a/docs/events.md b/docs/events.md index 227326a85..5b7558646 100644 --- a/docs/events.md +++ b/docs/events.md @@ -12,6 +12,11 @@ Explanations: * Event name: `OCA\Talk\Controller\RoomController::EVENT_BEFORE_ROOMS_GET` * Since: 8.0.0 +### Search listed conversations + +* Event class: `OCA\Talk\Events\UserEvent` +* Event name: `OCA\Talk\Controller\RoomController::EVENT_BEFORE_LISTED_ROOMS_GET` +* Since: 11.0.0 ### Create conversation diff --git a/lib/Controller/RoomController.php b/lib/Controller/RoomController.php index c05c98bdd..047fd72f7 100644 --- a/lib/Controller/RoomController.php +++ b/lib/Controller/RoomController.php @@ -68,6 +68,7 @@ use OCP\UserStatus\IUserStatus; class RoomController extends AEnvironmentAwareController { public const EVENT_BEFORE_ROOMS_GET = self::class . '::preGetRooms'; + public const EVENT_BEFORE_LISTED_ROOMS_GET = self::class . '::preGetListedRooms'; /** @var string|null */ protected $userId; @@ -220,6 +221,37 @@ class RoomController extends AEnvironmentAwareController { } /** + * Search listed rooms + * + * @NoAdminRequired + * + * @param string $searchTerm search term + * @return DataResponse + */ + public function getListedRooms(string $searchTerm = ''): DataResponse { + $event = new UserEvent($this->userId); + $this->dispatcher->dispatch(self::EVENT_BEFORE_LISTED_ROOMS_GET, $event); + + $rooms = $this->manager->getListedRoomsForUser($this->userId, $searchTerm); + + $return = []; + foreach ($rooms as $room) { + try { + $roomData = $this->formatRoom($room, null); + // since formatRoom will break early due to having no participant, + // we populate the remaining base attributes here + $return[] = $this->populateBaseRoomData($roomData, $room, $this->userId); + // TODO: should we populate more ? + } catch (RoomNotFoundException $e) { + } catch (\RuntimeException $e) { + } + } + + return new DataResponse($return, Http::STATUS_OK); + } + + + /** * @PublicPage * * @param string $token @@ -599,20 +631,12 @@ class RoomController extends AEnvironmentAwareController { $attendee = $currentParticipant->getAttendee(); $userId = $attendee->getActorType() === Attendee::ACTOR_USERS ? $attendee->getActorId() : ''; + $roomData = $this->populateBaseRoomData($roomData, $room, $userId); + $roomData = array_merge($roomData, [ - 'name' => $room->getName(), - 'displayName' => $room->getDisplayName($userId), - 'objectType' => $room->getObjectType(), - 'objectId' => $room->getObjectId(), 'participantType' => $attendee->getParticipantType(), - 'readOnly' => $room->getReadOnly(), - 'listable' => $room->getListable(), - 'hasCall' => $room->getActiveSince() instanceof \DateTimeInterface, - 'lastActivity' => $lastActivity, 'isFavorite' => $attendee->isFavorite(), 'notificationLevel' => $attendee->getNotificationLevel(), - 'lobbyState' => $room->getLobbyState(), - 'lobbyTimer' => $lobbyTimer, ]); if ($this->getAPIVersion() >= 3) { if ($this->talkConfig->isSIPConfigured()) { @@ -732,6 +756,33 @@ class RoomController extends AEnvironmentAwareController { return $roomData; } + protected function populateBaseRoomData(array $roomData, Room $room, $userId) { + $lastActivity = $room->getLastActivity(); + if ($lastActivity instanceof \DateTimeInterface) { + $lastActivity = $lastActivity->getTimestamp(); + } else { + $lastActivity = 0; + } + + $lobbyTimer = $room->getLobbyTimer(); + if ($lobbyTimer instanceof \DateTimeInterface) { + $lobbyTimer = $lobbyTimer->getTimestamp(); + } else { + $lobbyTimer = 0; + } + + return array_merge($roomData, [ + 'name' => $room->getName(), + 'displayName' => $room->getDisplayName($userId), + 'objectType' => $room->getObjectType(), + 'objectId' => $room->getObjectId(), + 'readOnly' => $room->getReadOnly(), + 'listable' => $room->getListable(), + 'hasCall' => $room->getActiveSince() instanceof \DateTimeInterface, + 'lobbyState' => $room->getLobbyState(), + ]); + } + /** * @param Room $room * @param Participant $participant diff --git a/lib/Manager.php b/lib/Manager.php index 4777142ee..e6efc5345 100644 --- a/lib/Manager.php +++ b/lib/Manager.php @@ -347,7 +347,7 @@ class Manager { $allowedRoomTypes = [Room::GROUP_CALL, Room::PUBLIC_CALL]; $allowedListedTypes = [Room::LISTABLE_ALL]; if (!$this->isGuestUser($userId)) { - $listedType[] = Room::LISTABLE_USERS; + $allowedListedTypes[] = Room::LISTABLE_USERS; } $query = $this->db->getQueryBuilder(); $query->select('r.*') diff --git a/src/components/LeftSidebar/ConversationsList/Conversation.vue b/src/components/LeftSidebar/ConversationsList/Conversation.vue index e34360c71..bb1013b18 100644 --- a/src/components/LeftSidebar/ConversationsList/Conversation.vue +++ b/src/components/LeftSidebar/ConversationsList/Conversation.vue @@ -45,7 +45,7 @@ :highlighted="counterShouldBePrimary"> <strong>{{ item.unreadMessages }}</strong> </AppNavigationCounter> - <template slot="actions"> + <template v-if="!isSearchResult" slot="actions"> <ActionButton v-if="canFavorite" :icon="iconFavorite" @click.prevent.exact="toggleFavoriteConversation"> @@ -124,6 +124,10 @@ export default { ConversationIcon, }, props: { + isSearchResult: { + type: Boolean, + default: false, + }, item: { type: Object, default: function() { diff --git a/src/components/LeftSidebar/LeftSidebar.vue b/src/components/LeftSidebar/LeftSidebar.vue index 5af424057..4b12bcca4 100644 --- a/src/components/LeftSidebar/LeftSidebar.vue +++ b/src/components/LeftSidebar/LeftSidebar.vue @@ -47,12 +47,11 @@ <template v-if="searchResultsListedConversations.length !== 0"> <Caption :title="t('spreed', 'Listed conversations')" /> - <li role="presentation"> - <!-- FIXME: use ConversationsList instead ? --> - <ConversationsOptionsList - :items="searchResultsListedConversations" - @click="joinConversation" /> - </li> + <Conversation + v-for="item of searchResultsListedConversations" + :key="item.id" + :item="item" + :is-search-result="true" /> </template> <template v-if="searchResultsUsers.length !== 0"> <Caption @@ -114,6 +113,7 @@ import AppNavigation from '@nextcloud/vue/dist/Components/AppNavigation' import Caption from '../Caption' import ConversationsList from './ConversationsList/ConversationsList' +import Conversation from './ConversationsList/Conversation' import ConversationsOptionsList from '../ConversationsOptionsList' import Hint from '../Hint' import SearchBox from './SearchBox/SearchBox' @@ -144,6 +144,7 @@ export default { Hint, SearchBox, NewGroupConversation, + Conversation, }, mixins: [ @@ -286,18 +287,7 @@ export default { async fetchListedConversations() { this.listedConversationsLoading = true const response = await searchListedConversations(this.searchText) - if (response.data.ocs.data?.entries?.length) { - this.searchResultsListedConversations = response.data.ocs.data.entries.map((result) => { - return { - // TODO: extract token ? - id: result.resourceUrl, - label: result.title, - icon: result.icon, - } - }) - } else { - this.searchResultsListedConversations = [] - } + this.searchResultsListedConversations = response.data.ocs.data this.listedConversationsLoading = false }, @@ -308,10 +298,6 @@ export default { this.focusInitialise() }, - async joinConversation(item) { - console.log('TODO: Implement join conversation: ', item) - }, - /** * Create a new conversation with the selected group/user/circle * @param {Object} item The autocomplete suggestion to start a conversation with diff --git a/src/services/conversationsService.js b/src/services/conversationsService.js index a4b8e41b2..b88a5bc18 100644 --- a/src/services/conversationsService.js +++ b/src/services/conversationsService.js @@ -90,19 +90,29 @@ const checkTalkVersionHash = function(response) { /** * Fetch listed conversations - * @param {string} searchText The string that will be used in the search query. + * @param {string} searchTerm The string that will be used in the search query. */ -const searchListedConversations = async function(searchText) { +const searchListedConversations = async function(searchTerm) { try { - // use search provider to find listed conversations - return axios.get(generateOcsUrl('search/providers/talk-listed-conversations', 2) + 'search', { + const response = await axios.get(generateOcsUrl('apps/spreed/api/v3', 2) + 'listed-room', { params: { - term: searchText, - format: 'json', + searchTerm, }, }) + + if (maintenanceWarning) { + maintenanceWarning.hideToast() + maintenanceWarning = null + } + + return response } catch (error) { - console.debug('Error while searching listedk conversations: ', error) + if (error.response && error.response.status === 503 && !maintenanceWarning) { + maintenanceWarning = showError(t('spreed', 'Nextcloud is in maintenance mode, please reload the page'), { + timeout: TOAST_PERMANENT_TIMEOUT, + }) + } + throw error } } /** |