Allow creating individual breakout rooms via the normal API

Signed-off-by: Joas Schilling <coding@schilljs.com>
This commit is contained in:
Joas Schilling 2022-12-12 13:05:01 +01:00
parent 616a0cb202
commit c87813021c
No known key found for this signature in database
GPG key ID: C400AAF20C1BB6FC
5 changed files with 137 additions and 21 deletions

View file

@ -96,16 +96,20 @@
## Creating a new conversation
*Note:* Creating a conversation as a child breakout room, will automatically set the lobby when breakout rooms are not started and will always overwrite the room type with the parent room type. Also moderators of the parent conversation will be automatically added as moderators.
* Method: `POST`
* Endpoint: `/room`
* Data:
| field | type | Description |
|------------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `roomType` | int | See [constants list](constants.md#conversation-types) |
| `invite` | string | user id (`roomType = 1`), group id (`roomType = 2` - optional), circle id (`roomType = 2`, `source = 'circles'`], only available with `circles-support` capability)) |
| `source` | string | The source for the invite, only supported on `roomType = 2` for `groups` and `circles` (only available with `circles-support` capability) |
| `roomName` | string | Conversation name up to 255 characters (Not available for `roomType = 1`) |
| field | type | Description |
|--------------|--------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `roomType` | int | See [constants list](constants.md#conversation-types) |
| `invite` | string | user id (`roomType = 1`), group id (`roomType = 2` - optional), circle id (`roomType = 2`, `source = 'circles'`], only available with `circles-support` capability)) |
| `source` | string | The source for the invite, only supported on `roomType = 2` for `groups` and `circles` (only available with `circles-support` capability) |
| `roomName` | string | Conversation name up to 255 characters (Not available for `roomType = 1`) |
| `objectType` | string | Type of an object this room references, currently only allowed value is `room` to indicate the parent of a breakout room |
| `objectId` | string | Id of an object this room references, room token is used for the parent of a breakout room |
* Response:
- Status code:

View file

@ -657,7 +657,7 @@ class RoomController extends AEnvironmentAwareController {
* @param string $source
* @return DataResponse
*/
public function createRoom(int $roomType, string $invite = '', string $roomName = '', string $source = ''): DataResponse {
public function createRoom(int $roomType, string $invite = '', string $roomName = '', string $source = '', string $objectType = '', string $objectId = ''): DataResponse {
if ($roomType !== Room::TYPE_ONE_TO_ONE) {
/** @var IUser $user */
$user = $this->userManager->get($this->userId);
@ -672,7 +672,7 @@ class RoomController extends AEnvironmentAwareController {
return $this->createOneToOneRoom($invite);
case Room::TYPE_GROUP:
if ($invite === '') {
return $this->createEmptyRoom($roomName, false);
return $this->createEmptyRoom($roomName, false, $objectType, $objectId);
}
if ($source === 'circles') {
return $this->createCircleRoom($invite);
@ -795,27 +795,63 @@ class RoomController extends AEnvironmentAwareController {
/**
* @NoAdminRequired
*
* @param string $roomName
* @param bool $public
* @return DataResponse
*/
protected function createEmptyRoom(string $roomName, bool $public = true): DataResponse {
protected function createEmptyRoom(string $roomName, bool $public = true, string $objectType = '', string $objectId = ''): DataResponse {
$currentUser = $this->userManager->get($this->userId);
if (!$currentUser instanceof IUser) {
return new DataResponse([], Http::STATUS_NOT_FOUND);
}
$roomType = $public ? Room::TYPE_PUBLIC : Room::TYPE_GROUP;
/** @var Room|null $parentRoom */
$parentRoom = null;
if ($objectType === BreakoutRoom::PARENT_OBJECT_TYPE) {
try {
$parentRoom = $this->manager->getRoomForUserByToken($objectId, $this->userId);
$parentRoomParticipant = $this->participantService->getParticipant($parentRoom, $this->userId);
if (!$parentRoomParticipant->hasModeratorPermissions()) {
return new DataResponse(['error' => 'permissions'], Http::STATUS_BAD_REQUEST);
}
if ($parentRoom->getBreakoutRoomMode() === BreakoutRoom::MODE_NOT_CONFIGURED) {
return new DataResponse(['error' => 'mode'], Http::STATUS_BAD_REQUEST);
}
// Overwriting the type with the parent type.
$roomType = $parentRoom->getType();
} catch (RoomNotFoundException $e) {
return new DataResponse(['error' => 'room'], Http::STATUS_BAD_REQUEST);
} catch (ParticipantNotFoundException $e) {
return new DataResponse(['error' => 'permissions'], Http::STATUS_BAD_REQUEST);
}
}
// Create the room
try {
$room = $this->roomService->createConversation($roomType, $roomName, $currentUser);
$room = $this->roomService->createConversation($roomType, $roomName, $currentUser, $objectType, $objectId);
} catch (InvalidArgumentException $e) {
return new DataResponse([], Http::STATUS_BAD_REQUEST);
}
return new DataResponse($this->formatRoom($room, $this->participantService->getParticipant($room, $currentUser->getUID(), false)), Http::STATUS_CREATED);
$currentParticipant = $this->participantService->getParticipant($room, $currentUser->getUID(), false);
if ($objectType === BreakoutRoom::PARENT_OBJECT_TYPE) {
// Enforce the lobby state when breakout rooms are disabled
if ($parentRoom instanceof Room && $parentRoom->getBreakoutRoomStatus() === BreakoutRoom::STATUS_STOPPED) {
$this->roomService->setLobby($room, Webinary::LOBBY_NON_MODERATORS, null, false, false);
}
$participants = $this->participantService->getParticipantsForRoom($parentRoom);
$moderators = array_filter($participants, static function (Participant $participant) use ($currentParticipant) {
return $participant->hasModeratorPermissions()
&& $participant->getAttendee()->getId() !== $currentParticipant->getAttendee()->getId();
});
if (!empty($moderators)) {
$this->breakoutRoomService->addModeratorsToBreakoutRooms([$room], $moderators);
}
}
return new DataResponse($this->formatRoom($room, $currentParticipant), Http::STATUS_CREATED);
}
/**

View file

@ -171,7 +171,7 @@ class BreakoutRoomService {
* @param Room[] $rooms
* @param Participant[] $moderators
*/
protected function addModeratorsToBreakoutRooms(array $rooms, array $moderators): void {
public function addModeratorsToBreakoutRooms(array $rooms, array $moderators): void {
$moderatorsToAdd = [];
foreach ($moderators as $moderator) {
$attendee = $moderator->getAttendee();

View file

@ -789,8 +789,20 @@ class FeatureContext implements Context, SnippetAcceptingContext {
* @param TableNode|null $formData
*/
public function userCreatesRoomWith(string $user, string $identifier, int $statusCode, string $apiVersion = 'v1', TableNode $formData = null): void {
$body = $formData->getRowsHash();
if (isset($body['objectType'], $body['objectId']) && $body['objectType'] === 'room') {
$result = preg_match('/ROOM\(([^)]+)\)/', $body['objectId'], $matches);
if ($result && isset(self::$identifierToToken[$matches[1]])) {
$body['objectId'] = self::$identifierToToken[$matches[1]];
} elseif ($result) {
throw new \InvalidArgumentException('Could not find parent room');
}
}
$this->setCurrentUser($user);
$this->sendRequest('POST', '/apps/spreed/api/' . $apiVersion . '/room', $formData);
$this->sendRequest('POST', '/apps/spreed/api/' . $apiVersion . '/room', $body);
$this->assertStatusCode($this->response, $statusCode);
$response = $this->getDataFromResponse($this->response);
@ -2367,12 +2379,12 @@ class FeatureContext implements Context, SnippetAcceptingContext {
* @param string $user
* @param int $amount
* @param string $modeString
* @param string $roomName
* @param string $identifier
* @param int $status
* @param string $apiVersion
* @param TableNode|null $formData
*/
public function userCreatesBreakoutRooms(string $user, int $amount, string $modeString, string $roomName, int $status, string $apiVersion, TableNode $formData = null): void {
public function userCreatesBreakoutRooms(string $user, int $amount, string $modeString, string $identifier, int $status, string $apiVersion, TableNode $formData = null): void {
switch ($modeString) {
case 'automatic':
$mode = 1;
@ -2396,14 +2408,14 @@ class FeatureContext implements Context, SnippetAcceptingContext {
$mapArray = [];
foreach ($formData->getRowsHash() as $attendee => $roomNumber) {
[$type, $id] = explode('::', $attendee);
$attendeeId = $this->getAttendeeId($type, $id, $roomName);
$attendeeId = $this->getAttendeeId($type, $id, $identifier);
$mapArray[$attendeeId] = (int) $roomNumber;
}
$data['attendeeMap'] = json_encode($mapArray, JSON_THROW_ON_ERROR);
}
$this->setCurrentUser($user);
$this->sendRequest('POST', '/apps/spreed/api/' . $apiVersion . '/breakout-rooms/' . self::$identifierToToken[$roomName], $data);
$this->sendRequest('POST', '/apps/spreed/api/' . $apiVersion . '/breakout-rooms/' . self::$identifierToToken[$identifier], $data);
$this->assertStatusCode($this->response, $status);
}

View file

@ -516,3 +516,67 @@ Feature: conversation/breakout-rooms
When user "participant1" deletes room "class room" with 200 (v4)
And user "participant1" is participant of the following rooms (v4)
And user "participant2" is participant of the following rooms (v4)
Scenario: Create an additional breakout room on the fly
Given user "participant1" creates room "class room" (v4)
| roomType | 2 |
| roomName | class room |
And user "participant1" adds user "participant2" to room "class room" with 200 (v4)
And user "participant1" promotes "participant2" in room "class room" with 200 (v4)
And user "participant1" sees the following attendees in room "class room" with 200 (v4)
| actorType | actorId | participantType |
| users | participant1 | 1 |
| users | participant2 | 2 |
And user "participant1" creates 2 automatic breakout rooms for "class room" with 200 (v1)
And user "participant1" is participant of the following rooms (v4)
| type | name | lobbyState | breakoutRoomMode | breakoutRoomStatus |
| 2 | class room | 0 | 1 | 0 |
| 2 | Room 1 | 1 | 0 | 0 |
| 2 | Room 2 | 1 | 0 | 0 |
And user "participant2" is participant of the following rooms (v4)
| type | name | lobbyState | breakoutRoomMode | breakoutRoomStatus |
| 2 | class room | 0 | 1 | 0 |
| 2 | Room 1 | 1 | 0 | 0 |
| 2 | Room 2 | 1 | 0 | 0 |
# Can not nest
Given user "participant1" creates room "Room 3" with 400 (v4)
| roomType | 2 |
| roomName | Room 3 |
| objectType | room |
| objectId | ROOM(Room 2) |
Given user "participant1" creates room "Room 3" with 201 (v4)
| roomType | 2 |
| roomName | Room 3 |
| objectType | room |
| objectId | ROOM(class room) |
And user "participant1" is participant of the following rooms (v4)
| type | name | lobbyState | breakoutRoomMode | breakoutRoomStatus |
| 2 | class room | 0 | 1 | 0 |
| 2 | Room 1 | 1 | 0 | 0 |
| 2 | Room 2 | 1 | 0 | 0 |
| 2 | Room 3 | 1 | 0 | 0 |
And user "participant2" is participant of the following rooms (v4)
| type | name | lobbyState | breakoutRoomMode | breakoutRoomStatus |
| 2 | class room | 0 | 1 | 0 |
| 2 | Room 1 | 1 | 0 | 0 |
| 2 | Room 2 | 1 | 0 | 0 |
| 2 | Room 3 | 1 | 0 | 0 |
And user "participant1" starts breakout rooms in room "class room" with 200 (v1)
And user "participant1" is participant of the following rooms (v4)
| type | name | lobbyState | breakoutRoomMode | breakoutRoomStatus |
| 2 | class room | 0 | 1 | 1 |
| 2 | Room 1 | 0 | 0 | 0 |
| 2 | Room 2 | 0 | 0 | 0 |
| 2 | Room 3 | 0 | 0 | 0 |
Given user "participant1" creates room "Room 3" with 201 (v4)
| roomType | 2 |
| roomName | Room 4 |
| objectType | room |
| objectId | ROOM(class room) |
And user "participant1" is participant of the following rooms (v4)
| type | name | lobbyState | breakoutRoomMode | breakoutRoomStatus |
| 2 | class room | 0 | 1 | 1 |
| 2 | Room 1 | 0 | 0 | 0 |
| 2 | Room 2 | 0 | 0 | 0 |
| 2 | Room 3 | 0 | 0 | 0 |
| 2 | Room 4 | 0 | 0 | 0 |