mirror of
https://github.com/nextcloud/spreed.git
synced 2025-07-21 10:37:10 +00:00
feat(threads): Support federated conversations
Signed-off-by: Joas Schilling <coding@schilljs.com>
This commit is contained in:
@ -10,6 +10,7 @@ namespace OCA\Talk\Controller;
|
||||
|
||||
use OCA\Talk\Chat\ChatManager;
|
||||
use OCA\Talk\Chat\MessageParser;
|
||||
use OCA\Talk\Middleware\Attribute\FederationSupported;
|
||||
use OCA\Talk\Middleware\Attribute\RequireModeratorOrNoLobby;
|
||||
use OCA\Talk\Middleware\Attribute\RequireParticipant;
|
||||
use OCA\Talk\Middleware\Attribute\RequirePermission;
|
||||
@ -23,6 +24,7 @@ use OCP\AppFramework\Db\DoesNotExistException;
|
||||
use OCP\AppFramework\Http;
|
||||
use OCP\AppFramework\Http\Attribute\ApiRoute;
|
||||
use OCP\AppFramework\Http\Attribute\PublicPage;
|
||||
use OCP\AppFramework\Http\Attribute\RequestHeader;
|
||||
use OCP\AppFramework\Http\DataResponse;
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use OCP\Comments\NotFoundException;
|
||||
@ -58,14 +60,22 @@ class ThreadController extends AEnvironmentAwareOCSController {
|
||||
*
|
||||
* 200: List of threads returned
|
||||
*/
|
||||
#[FederationSupported]
|
||||
#[PublicPage]
|
||||
#[RequireModeratorOrNoLobby]
|
||||
#[RequireParticipant]
|
||||
#[RequestHeader(name: 'x-nextcloud-federation', description: 'Set to 1 when the request is performed by another Nextcloud Server to indicate a federation request', indirect: true)]
|
||||
#[ApiRoute(verb: 'GET', url: '/api/{apiVersion}/chat/{token}/threads/recent', requirements: [
|
||||
'apiVersion' => '(v1)',
|
||||
'token' => '[a-z0-9]{4,30}',
|
||||
])]
|
||||
public function getRecentActiveThreads(int $limit = 50): DataResponse {
|
||||
if ($this->room->isFederatedConversation()) {
|
||||
/** @var \OCA\Talk\Federation\Proxy\TalkV1\Controller\ThreadController $proxy */
|
||||
$proxy = \OCP\Server::get(\OCA\Talk\Federation\Proxy\TalkV1\Controller\ThreadController::class);
|
||||
return $proxy->getRecentActiveThreads($this->room, $this->participant, $limit);
|
||||
}
|
||||
|
||||
$threads = $this->threadService->getRecentByRoomId($this->room, $limit);
|
||||
$list = $this->prepareListOfThreads($threads);
|
||||
return new DataResponse($list);
|
||||
@ -76,20 +86,28 @@ class ThreadController extends AEnvironmentAwareOCSController {
|
||||
*
|
||||
* @param int $threadId The thread ID to get the info for
|
||||
* @psalm-param non-negative-int $threadId
|
||||
* @return DataResponse<Http::STATUS_OK, TalkThreadInfo, array{}>|DataResponse<Http::STATUS_NOT_FOUND, array{error: 'thread'}, array{}>
|
||||
* @return DataResponse<Http::STATUS_OK, TalkThreadInfo, array{}>|DataResponse<Http::STATUS_NOT_FOUND, array{error: 'thread'|'status'}, array{}>
|
||||
*
|
||||
* 200: Thread info returned
|
||||
* 404: Thread not found
|
||||
*/
|
||||
#[FederationSupported]
|
||||
#[PublicPage]
|
||||
#[RequireModeratorOrNoLobby]
|
||||
#[RequireParticipant]
|
||||
#[RequestHeader(name: 'x-nextcloud-federation', description: 'Set to 1 when the request is performed by another Nextcloud Server to indicate a federation request', indirect: true)]
|
||||
#[ApiRoute(verb: 'GET', url: '/api/{apiVersion}/chat/{token}/threads/{threadId}', requirements: [
|
||||
'apiVersion' => '(v1)',
|
||||
'token' => '[a-z0-9]{4,30}',
|
||||
'threadId' => '[0-9]+',
|
||||
])]
|
||||
public function getThread(int $threadId): DataResponse {
|
||||
if ($this->room->isFederatedConversation()) {
|
||||
/** @var \OCA\Talk\Federation\Proxy\TalkV1\Controller\ThreadController $proxy */
|
||||
$proxy = \OCP\Server::get(\OCA\Talk\Federation\Proxy\TalkV1\Controller\ThreadController::class);
|
||||
return $proxy->getThread($this->room, $this->participant, $threadId);
|
||||
}
|
||||
|
||||
try {
|
||||
$thread = $this->threadService->findByThreadId($threadId);
|
||||
} catch (DoesNotExistException) {
|
||||
@ -162,23 +180,31 @@ class ThreadController extends AEnvironmentAwareOCSController {
|
||||
*
|
||||
* @param int $messageId The message to create a thread for (Doesn't have to be the root)
|
||||
* @psalm-param non-negative-int $messageId
|
||||
* @return DataResponse<Http::STATUS_OK, TalkThreadInfo, array{}>|DataResponse<Http::STATUS_BAD_REQUEST|Http::STATUS_NOT_FOUND, array{error: 'message'|'top-most'}, array{}>
|
||||
* @return DataResponse<Http::STATUS_OK, TalkThreadInfo, array{}>|DataResponse<Http::STATUS_BAD_REQUEST|Http::STATUS_NOT_FOUND, array{error: 'message'|'status'|'top-most'}, array{}>
|
||||
*
|
||||
* 200: Thread successfully created
|
||||
* 400: Root message is a system message and therefor not supported
|
||||
* 404: Message or top most message not found
|
||||
*/
|
||||
#[FederationSupported]
|
||||
#[PublicPage]
|
||||
#[RequireModeratorOrNoLobby]
|
||||
#[RequireParticipant]
|
||||
#[RequirePermission(permission: RequirePermission::CHAT)]
|
||||
#[RequireReadWriteConversation]
|
||||
#[RequestHeader(name: 'x-nextcloud-federation', description: 'Set to 1 when the request is performed by another Nextcloud Server to indicate a federation request', indirect: true)]
|
||||
#[ApiRoute(verb: 'POST', url: '/api/{apiVersion}/chat/{token}/threads/{messageId}', requirements: [
|
||||
'apiVersion' => '(v1)',
|
||||
'token' => '[a-z0-9]{4,30}',
|
||||
'messageId' => '[0-9]+',
|
||||
])]
|
||||
public function makeThread(int $messageId): DataResponse {
|
||||
if ($this->room->isFederatedConversation()) {
|
||||
/** @var \OCA\Talk\Federation\Proxy\TalkV1\Controller\ThreadController $proxy */
|
||||
$proxy = \OCP\Server::get(\OCA\Talk\Federation\Proxy\TalkV1\Controller\ThreadController::class);
|
||||
return $proxy->makeThread($this->room, $this->participant, $messageId);
|
||||
}
|
||||
|
||||
try {
|
||||
// Todo: What if the root already expired
|
||||
$comment = $this->chatManager->getTopMostComment($this->room, (string)$messageId);
|
||||
|
145
lib/Federation/Proxy/TalkV1/Controller/ThreadController.php
Normal file
145
lib/Federation/Proxy/TalkV1/Controller/ThreadController.php
Normal file
@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\Talk\Federation\Proxy\TalkV1\Controller;
|
||||
|
||||
use OCA\Talk\Chat\Notifier;
|
||||
use OCA\Talk\Exceptions\CannotReachRemoteException;
|
||||
use OCA\Talk\Federation\Proxy\TalkV1\ProxyRequest;
|
||||
use OCA\Talk\Federation\Proxy\TalkV1\UserConverter;
|
||||
use OCA\Talk\Participant;
|
||||
use OCA\Talk\ResponseDefinitions;
|
||||
use OCA\Talk\Room;
|
||||
use OCA\Talk\Service\ParticipantService;
|
||||
use OCA\Talk\Service\RoomFormatter;
|
||||
use OCP\AppFramework\Http;
|
||||
use OCP\AppFramework\Http\DataResponse;
|
||||
use OCP\ICacheFactory;
|
||||
|
||||
/**
|
||||
* @psalm-import-type TalkThreadInfo from ResponseDefinitions
|
||||
* @psalm-import-type TalkRoom from ResponseDefinitions
|
||||
*/
|
||||
class ThreadController {
|
||||
|
||||
public function __construct(
|
||||
protected ProxyRequest $proxy,
|
||||
protected UserConverter $userConverter,
|
||||
protected ParticipantService $participantService,
|
||||
protected RoomFormatter $roomFormatter,
|
||||
protected Notifier $notifier,
|
||||
ICacheFactory $cacheFactory,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see \OCA\Talk\Controller\ThreadController::getRecentActiveThreads()
|
||||
*
|
||||
* @return DataResponse<Http::STATUS_OK, list<TalkThreadInfo>, array{}>
|
||||
* @throws CannotReachRemoteException
|
||||
*
|
||||
* 200: List of threads returned
|
||||
*/
|
||||
public function getRecentActiveThreads(Room $room, Participant $participant, int $limit): DataResponse {
|
||||
$proxy = $this->proxy->get(
|
||||
$participant->getAttendee()->getInvitedCloudId(),
|
||||
$participant->getAttendee()->getAccessToken(),
|
||||
$room->getRemoteServer() . '/ocs/v2.php/apps/spreed/api/v1/chat/' . $room->getRemoteToken() . '/threads/recent',
|
||||
[
|
||||
'limit' => $limit,
|
||||
],
|
||||
);
|
||||
|
||||
/** @var list<TalkThreadInfo> $data */
|
||||
$data = $this->proxy->getOCSData($proxy);
|
||||
if (!empty($data)) {
|
||||
$data = $this->userConverter->convertThreadInfos($room, $data);
|
||||
}
|
||||
|
||||
return new DataResponse($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see \OCA\Talk\Controller\ThreadController::getThread()
|
||||
*
|
||||
* @psalm-param non-negative-int $threadId
|
||||
* @return DataResponse<Http::STATUS_OK, TalkThreadInfo, array{}>|DataResponse<Http::STATUS_NOT_FOUND, array{error: 'thread'|'status'}, array{}>
|
||||
* @throws CannotReachRemoteException
|
||||
*
|
||||
* 200: Thread info returned
|
||||
* 404: Thread not found
|
||||
*/
|
||||
public function getThread(Room $room, Participant $participant, int $threadId): DataResponse {
|
||||
$proxy = $this->proxy->get(
|
||||
$participant->getAttendee()->getInvitedCloudId(),
|
||||
$participant->getAttendee()->getAccessToken(),
|
||||
$room->getRemoteServer() . '/ocs/v2.php/apps/spreed/api/v1/chat/' . $room->getRemoteToken() . '/threads/' . $threadId,
|
||||
);
|
||||
|
||||
$statusCode = $proxy->getStatusCode();
|
||||
if ($statusCode !== Http::STATUS_OK) {
|
||||
if ($statusCode !== Http::STATUS_NOT_FOUND) {
|
||||
$this->proxy->logUnexpectedStatusCode(__METHOD__, $statusCode);
|
||||
$data = ['error' => 'status'];
|
||||
} else {
|
||||
/** @var array{error: 'thread'} $data */
|
||||
$data = $this->proxy->getOCSData($proxy, [Http::STATUS_NOT_FOUND]);
|
||||
}
|
||||
return new DataResponse($data, Http::STATUS_NOT_FOUND);
|
||||
}
|
||||
|
||||
/** @var TalkThreadInfo $data */
|
||||
$data = $this->proxy->getOCSData($proxy);
|
||||
$data = $this->userConverter->convertThreadInfo($room, $data);
|
||||
|
||||
return new DataResponse($data, Http::STATUS_OK);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see \OCA\Talk\Controller\ThreadController::makeThread()
|
||||
*
|
||||
* @psalm-param non-negative-int $messageId
|
||||
* @return DataResponse<Http::STATUS_OK, TalkThreadInfo, array{}>|DataResponse<Http::STATUS_BAD_REQUEST|Http::STATUS_NOT_FOUND, array{error: 'message'|'status'|'top-most'}, array{}>
|
||||
* @throws CannotReachRemoteException
|
||||
*
|
||||
* 200: Thread info returned
|
||||
* 404: Thread not found
|
||||
*/
|
||||
public function makeThread(Room $room, Participant $participant, int $messageId): DataResponse {
|
||||
$proxy = $this->proxy->post(
|
||||
$participant->getAttendee()->getInvitedCloudId(),
|
||||
$participant->getAttendee()->getAccessToken(),
|
||||
$room->getRemoteServer() . '/ocs/v2.php/apps/spreed/api/v1/chat/' . $room->getRemoteToken() . '/threads/' . $messageId,
|
||||
);
|
||||
|
||||
$statusCode = $proxy->getStatusCode();
|
||||
if ($statusCode !== Http::STATUS_OK) {
|
||||
if (!in_array($statusCode, [
|
||||
Http::STATUS_BAD_REQUEST,
|
||||
Http::STATUS_NOT_FOUND,
|
||||
], true)) {
|
||||
$statusCode = $this->proxy->logUnexpectedStatusCode(__METHOD__, $statusCode);
|
||||
$data = ['error' => 'status'];
|
||||
} else {
|
||||
/** @var array{error: 'message'|'top-most'} $data */
|
||||
$data = $this->proxy->getOCSData($proxy, [
|
||||
Http::STATUS_BAD_REQUEST,
|
||||
Http::STATUS_NOT_FOUND,
|
||||
]);
|
||||
}
|
||||
return new DataResponse($data, $statusCode);
|
||||
}
|
||||
|
||||
/** @var TalkThreadInfo $data */
|
||||
$data = $this->proxy->getOCSData($proxy);
|
||||
$data = $this->userConverter->convertThreadInfo($room, $data);
|
||||
|
||||
return new DataResponse($data, Http::STATUS_OK);
|
||||
}
|
||||
}
|
@ -20,6 +20,7 @@ use OCA\Talk\Service\ParticipantService;
|
||||
* @psalm-import-type TalkPoll from ResponseDefinitions
|
||||
* @psalm-import-type TalkPollDraft from ResponseDefinitions
|
||||
* @psalm-import-type TalkReaction from ResponseDefinitions
|
||||
* @psalm-import-type TalkThreadInfo from ResponseDefinitions
|
||||
*/
|
||||
class UserConverter {
|
||||
/**
|
||||
@ -158,6 +159,34 @@ class UserConverter {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Room $room
|
||||
* @param TalkThreadInfo $threadInfo
|
||||
* @return TalkThreadInfo
|
||||
*/
|
||||
public function convertThreadInfo(Room $room, array $threadInfo): array {
|
||||
$threadInfo['thread']['roomToken'] = $room->getToken();
|
||||
if (isset($threadInfo['first'])) {
|
||||
$threadInfo['first'] = $this->convertMessageParameters($room, $threadInfo['first']);
|
||||
}
|
||||
if (isset($threadInfo['last'])) {
|
||||
$threadInfo['last'] = $this->convertMessageParameters($room, $threadInfo['last']);
|
||||
}
|
||||
return $threadInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Room $room
|
||||
* @param list<TalkThreadInfo> $threadInfos
|
||||
* @return list<TalkThreadInfo>
|
||||
*/
|
||||
public function convertThreadInfos(Room $room, array $threadInfos): array {
|
||||
return array_map(
|
||||
fn (array $threadInfo): array => $this->convertThreadInfo($room, $threadInfo),
|
||||
$threadInfos
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T of TalkPoll|TalkPollDraft
|
||||
* @param Room $room
|
||||
|
Reference in New Issue
Block a user