mirror of https://github.com/sualko/cloud_bbb
parent
de834240dd
commit
dc30ec0e3e
|
@ -2,6 +2,7 @@
|
||||||
return [
|
return [
|
||||||
'resources' => [
|
'resources' => [
|
||||||
'room' => ['url' => '/rooms'],
|
'room' => ['url' => '/rooms'],
|
||||||
|
'roomShare' => ['url' => '/roomShares'],
|
||||||
'room_api' => ['url' => '/api/0.1/rooms'],
|
'room_api' => ['url' => '/api/0.1/rooms'],
|
||||||
],
|
],
|
||||||
'routes' => [
|
'routes' => [
|
||||||
|
|
|
@ -10,8 +10,11 @@ use BigBlueButton\Core\Record;
|
||||||
use BigBlueButton\Parameters\DeleteRecordingsParameters;
|
use BigBlueButton\Parameters\DeleteRecordingsParameters;
|
||||||
use BigBlueButton\Parameters\IsMeetingRunningParameters;
|
use BigBlueButton\Parameters\IsMeetingRunningParameters;
|
||||||
use OCA\BigBlueButton\Db\Room;
|
use OCA\BigBlueButton\Db\Room;
|
||||||
|
use OCA\BigBlueButton\Db\RoomShare;
|
||||||
|
use OCA\BigBlueButton\Service\RoomShareService;
|
||||||
use OCP\IConfig;
|
use OCP\IConfig;
|
||||||
use OCP\IURLGenerator;
|
use OCP\IURLGenerator;
|
||||||
|
use OCP\IGroupManager;
|
||||||
|
|
||||||
class API
|
class API
|
||||||
{
|
{
|
||||||
|
@ -21,15 +24,25 @@ class API
|
||||||
/** @var IURLGenerator */
|
/** @var IURLGenerator */
|
||||||
private $urlGenerator;
|
private $urlGenerator;
|
||||||
|
|
||||||
|
/** @var IGroupManager */
|
||||||
|
private $groupManager;
|
||||||
|
|
||||||
|
/** @var RoomShareService */
|
||||||
|
private $roomShareService;
|
||||||
|
|
||||||
/** @var BigBlueButton */
|
/** @var BigBlueButton */
|
||||||
private $server;
|
private $server;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
IConfig $config,
|
IConfig $config,
|
||||||
IURLGenerator $urlGenerator
|
IURLGenerator $urlGenerator,
|
||||||
|
IGroupManager $groupManager,
|
||||||
|
RoomShareService $roomShareService
|
||||||
) {
|
) {
|
||||||
$this->config = $config;
|
$this->config = $config;
|
||||||
$this->urlGenerator = $urlGenerator;
|
$this->urlGenerator = $urlGenerator;
|
||||||
|
$this->groupManager = $groupManager;
|
||||||
|
$this->roomShareService = $roomShareService;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getServer()
|
private function getServer()
|
||||||
|
@ -51,7 +64,7 @@ class API
|
||||||
*/
|
*/
|
||||||
public function createJoinUrl(Room $room, int $creationTime, string $displayname, string $uid = null)
|
public function createJoinUrl(Room $room, int $creationTime, string $displayname, string $uid = null)
|
||||||
{
|
{
|
||||||
$password = $uid === $room->userId ? $room->moderatorPassword : $room->attendeePassword;
|
$password = $this->isModerator($room, $uid) ? $room->moderatorPassword : $room->attendeePassword;
|
||||||
|
|
||||||
$joinMeetingParams = new JoinMeetingParameters($room->uid, $displayname, $password);
|
$joinMeetingParams = new JoinMeetingParameters($room->uid, $displayname, $password);
|
||||||
|
|
||||||
|
@ -68,6 +81,38 @@ class API
|
||||||
return $this->getServer()->getJoinMeetingURL($joinMeetingParams);
|
return $this->getServer()->getJoinMeetingURL($joinMeetingParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function isModerator(Room $room, string $uid): bool
|
||||||
|
{
|
||||||
|
if ($uid === null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($uid === $room->userId) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$shares = $this->roomShareService->findAll($room->id);
|
||||||
|
|
||||||
|
/** @var RoomShare $share */
|
||||||
|
foreach ($shares as $share) {
|
||||||
|
if (!$share->hasModeratorPermission()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($share->getShareType() === RoomShare::SHARE_TYPE_USER) {
|
||||||
|
if ($share->getShareWith() === $uid) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} elseif ($share->getShareType() === RoomShare::SHARE_TYPE_GROUP) {
|
||||||
|
if ($this->groupManager->isInGroup($uid, $share->getShareWith())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create meeting room.
|
* Create meeting room.
|
||||||
*
|
*
|
||||||
|
|
|
@ -3,21 +3,28 @@
|
||||||
namespace OCA\BigBlueButton\Controller;
|
namespace OCA\BigBlueButton\Controller;
|
||||||
|
|
||||||
use Closure;
|
use Closure;
|
||||||
|
use Exception;
|
||||||
use OCP\AppFramework\Http;
|
use OCP\AppFramework\Http;
|
||||||
use OCP\AppFramework\Http\DataResponse;
|
use OCP\AppFramework\Http\DataResponse;
|
||||||
|
|
||||||
use OCA\BigBlueButton\Service\RoomNotFound;
|
use OCA\BigBlueButton\Service\RoomNotFound;
|
||||||
|
use OCA\BigBlueButton\Service\RoomShareNotFound;
|
||||||
|
|
||||||
trait Errors
|
trait Errors
|
||||||
{
|
{
|
||||||
protected function handleNotFound(Closure $callback): DataResponse
|
protected function handleNotFound(Closure $callback): DataResponse
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
return new DataResponse($callback());
|
$return = $callback();
|
||||||
} catch (RoomNotFound $e) {
|
return ($return instanceof DataResponse) ? $return : new DataResponse($return);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
if ($e instanceof RoomNotFound ||
|
||||||
|
$e instanceof RoomShareNotFound) {
|
||||||
$message = ['message' => $e->getMessage()];
|
$message = ['message' => $e->getMessage()];
|
||||||
return new DataResponse($message, Http::STATUS_NOT_FOUND);
|
return new DataResponse($message, Http::STATUS_NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,153 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace OCA\BigBlueButton\Controller;
|
||||||
|
|
||||||
|
use OCA\BigBlueButton\Db\RoomShare;
|
||||||
|
use OCA\BigBlueButton\Service\RoomService;
|
||||||
|
use OCA\BigBlueButton\Service\RoomShareNotFound;
|
||||||
|
use OCP\IRequest;
|
||||||
|
use OCP\AppFramework\Http;
|
||||||
|
use OCP\AppFramework\Http\DataResponse;
|
||||||
|
use OCP\AppFramework\Controller;
|
||||||
|
|
||||||
|
use OCA\BigBlueButton\Service\RoomShareService;
|
||||||
|
use OCP\IUserManager;
|
||||||
|
|
||||||
|
class RoomShareController extends Controller
|
||||||
|
{
|
||||||
|
/** @var RoomShareService */
|
||||||
|
private $service;
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
private $userId;
|
||||||
|
|
||||||
|
/** @var IUserManager */
|
||||||
|
private $userManager;
|
||||||
|
|
||||||
|
/** @var RoomService */
|
||||||
|
private $roomService;
|
||||||
|
|
||||||
|
use Errors;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
$appName,
|
||||||
|
IRequest $request,
|
||||||
|
RoomShareService $service,
|
||||||
|
IUserManager $userManager,
|
||||||
|
RoomService $roomService,
|
||||||
|
$userId
|
||||||
|
) {
|
||||||
|
parent::__construct($appName, $request);
|
||||||
|
$this->service = $service;
|
||||||
|
$this->userManager = $userManager;
|
||||||
|
$this->roomService = $roomService;
|
||||||
|
$this->userId = $userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
*/
|
||||||
|
public function index(): DataResponse
|
||||||
|
{
|
||||||
|
$roomId = $this->request->getParam('id');
|
||||||
|
|
||||||
|
if ($roomId === null) {
|
||||||
|
return new DataResponse([], Http::STATUS_BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->isUserAllowed($roomId)) {
|
||||||
|
return new DataResponse([], Http::STATUS_FORBIDDEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
$roomShares = $this->service->findAll($roomId);
|
||||||
|
|
||||||
|
/** @var RoomShare $roomShare */
|
||||||
|
foreach ($roomShares as $roomShare) {
|
||||||
|
$shareWithUser = $this->userManager->get($roomShare->getShareWith());
|
||||||
|
|
||||||
|
if ($shareWithUser !== null) {
|
||||||
|
$roomShare->setShareWithDisplayName($shareWithUser->getDisplayName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new DataResponse($roomShares);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
*/
|
||||||
|
public function create(
|
||||||
|
int $roomId,
|
||||||
|
int $shareType,
|
||||||
|
string $shareWith,
|
||||||
|
int $permission
|
||||||
|
): DataResponse {
|
||||||
|
if (!$this->isUserAllowed($roomId)) {
|
||||||
|
return new DataResponse(null, Http::STATUS_FORBIDDEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new DataResponse($this->service->create(
|
||||||
|
$roomId,
|
||||||
|
$shareType,
|
||||||
|
$shareWith,
|
||||||
|
$permission
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
*/
|
||||||
|
public function update(
|
||||||
|
int $id,
|
||||||
|
int $roomId,
|
||||||
|
int $shareType,
|
||||||
|
string $shareWith,
|
||||||
|
int $permission
|
||||||
|
): DataResponse {
|
||||||
|
if (!$this->isUserAllowed($roomId)) {
|
||||||
|
return new DataResponse(null, Http::STATUS_FORBIDDEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->handleNotFound(function () use (
|
||||||
|
$id,
|
||||||
|
$roomId,
|
||||||
|
$shareType,
|
||||||
|
$shareWith,
|
||||||
|
$permission) {
|
||||||
|
return $this->service->update(
|
||||||
|
$id,
|
||||||
|
$roomId,
|
||||||
|
$shareType,
|
||||||
|
$shareWith,
|
||||||
|
$permission
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
*/
|
||||||
|
public function destroy(int $id): DataResponse
|
||||||
|
{
|
||||||
|
return $this->handleNotFound(function () use ($id) {
|
||||||
|
$roomShare = $this->service->find($id);
|
||||||
|
|
||||||
|
if (!$this->isUserAllowed($roomShare->getRoomId())) {
|
||||||
|
return new DataResponse(null, Http::STATUS_FORBIDDEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->service->delete($id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private function isUserAllowed(int $roomId): bool
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$room = $this->roomService->find($roomId, $this->userId);
|
||||||
|
|
||||||
|
return $room !== null;
|
||||||
|
} catch (RoomShareNotFound $e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
<?php
|
||||||
|
namespace OCA\BigBlueButton\Db;
|
||||||
|
|
||||||
|
use JsonSerializable;
|
||||||
|
|
||||||
|
use OCP\AppFramework\Db\Entity;
|
||||||
|
|
||||||
|
class RoomShare extends Entity implements JsonSerializable
|
||||||
|
{
|
||||||
|
const PERMISSION_ADMIN = 0;
|
||||||
|
const PERMISSION_MODERATOR = 1;
|
||||||
|
const PERMISSION_USER = 2;
|
||||||
|
|
||||||
|
const SHARE_TYPE_USER = 0;
|
||||||
|
const SHARE_TYPE_GROUP = 1;
|
||||||
|
|
||||||
|
protected $roomId;
|
||||||
|
protected $shareType;
|
||||||
|
protected $shareWith;
|
||||||
|
protected $shareWithDisplayName;
|
||||||
|
protected $permission;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->addType('roomId', 'integer');
|
||||||
|
$this->addType('shareType', 'integer');
|
||||||
|
$this->addType('permission', 'integer');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function jsonSerialize(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id' => $this->id,
|
||||||
|
'roomId' => $this->roomId,
|
||||||
|
'shareType' => $this->shareType,
|
||||||
|
'shareWith' => $this->shareWith,
|
||||||
|
'shareWithDisplayName' => $this->shareWithDisplayName,
|
||||||
|
'permission' => $this->permission,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasUserPermission(): bool
|
||||||
|
{
|
||||||
|
return $this->permission === self::PERMISSION_ADMIN || $this->permission === self::PERMISSION_MODERATOR || $this->permission === self::PERMISSION_USER;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasModeratorPermission(): bool
|
||||||
|
{
|
||||||
|
return $this->permission === self::PERMISSION_ADMIN || $this->permission === self::PERMISSION_MODERATOR;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasAdminPermission(): bool
|
||||||
|
{
|
||||||
|
return $this->permission === self::PERMISSION_ADMIN;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
<?php
|
||||||
|
namespace OCA\BigBlueButton\Db;
|
||||||
|
|
||||||
|
use OCP\AppFramework\Db\DoesNotExistException;
|
||||||
|
use OCP\AppFramework\Db\Entity;
|
||||||
|
use OCP\AppFramework\Db\QBMapper;
|
||||||
|
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||||
|
use OCP\IDBConnection;
|
||||||
|
|
||||||
|
class RoomShareMapper extends QBMapper
|
||||||
|
{
|
||||||
|
public function __construct(IDBConnection $db)
|
||||||
|
{
|
||||||
|
parent::__construct($db, 'bbb_room_shares', RoomShare::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $id
|
||||||
|
* @return Entity|RoomShare
|
||||||
|
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
|
||||||
|
* @throws DoesNotExistException
|
||||||
|
*/
|
||||||
|
public function find(int $id): RoomShare
|
||||||
|
{
|
||||||
|
/* @var $qb IQueryBuilder */
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->select('*')
|
||||||
|
->from('bbb_room_shares')
|
||||||
|
->where($qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
|
||||||
|
return $this->findEntity($qb);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findAll(int $roomId): array
|
||||||
|
{
|
||||||
|
/* @var $qb IQueryBuilder */
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->select('*')
|
||||||
|
->from('bbb_room_shares')
|
||||||
|
->where($qb->expr()->eq('room_id', $qb->createNamedParameter($roomId, IQueryBuilder::PARAM_INT)));
|
||||||
|
return $this->findEntities($qb);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace OCA\BigBlueButton\Migration;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use OCP\DB\ISchemaWrapper;
|
||||||
|
use OCP\Migration\IOutput;
|
||||||
|
use OCP\Migration\SimpleMigrationStep;
|
||||||
|
|
||||||
|
class Version000000Date20200613111242 extends SimpleMigrationStep
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param IOutput $output
|
||||||
|
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
|
||||||
|
* @param array $options
|
||||||
|
* @return null|ISchemaWrapper
|
||||||
|
*/
|
||||||
|
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options)
|
||||||
|
{
|
||||||
|
/** @var ISchemaWrapper $schema */
|
||||||
|
$schema = $schemaClosure();
|
||||||
|
|
||||||
|
if (!$schema->hasTable('bbb_room_shares')) {
|
||||||
|
$table = $schema->createTable('bbb_room_shares');
|
||||||
|
$table->addColumn('id', 'integer', [
|
||||||
|
'autoincrement' => true,
|
||||||
|
'notnull' => true,
|
||||||
|
]);
|
||||||
|
$table->addColumn('room_id', 'integer', [
|
||||||
|
'notnull' => true,
|
||||||
|
]);
|
||||||
|
$table->addColumn('share_with', 'string', [
|
||||||
|
'notnull' => true,
|
||||||
|
'length' => 200,
|
||||||
|
]);
|
||||||
|
$table->addColumn('share_type', 'integer', [
|
||||||
|
'notnull' => true,
|
||||||
|
]);
|
||||||
|
$table->addColumn('permission', 'integer', [
|
||||||
|
'notnull' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$table->setPrimaryKey(['id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $schema;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace OCA\BigBlueButton\Service;
|
||||||
|
|
||||||
|
class RoomShareNotFound extends \Exception
|
||||||
|
{
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
<?php
|
||||||
|
namespace OCA\BigBlueButton\Service;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
use OCP\AppFramework\Db\DoesNotExistException;
|
||||||
|
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
|
||||||
|
|
||||||
|
use OCA\BigBlueButton\Db\RoomShare;
|
||||||
|
use OCA\BigBlueButton\Db\RoomShareMapper;
|
||||||
|
|
||||||
|
class RoomShareService
|
||||||
|
{
|
||||||
|
|
||||||
|
/** @var RoomShareMapper */
|
||||||
|
private $mapper;
|
||||||
|
|
||||||
|
public function __construct(RoomShareMapper $mapper)
|
||||||
|
{
|
||||||
|
$this->mapper = $mapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findAll(int $roomId): array
|
||||||
|
{
|
||||||
|
return $this->mapper->findAll($roomId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function handleException(Exception $e): void
|
||||||
|
{
|
||||||
|
if ($e instanceof DoesNotExistException ||
|
||||||
|
$e instanceof MultipleObjectsReturnedException) {
|
||||||
|
throw new RoomShareNotFound($e->getMessage());
|
||||||
|
} else {
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function find($id): RoomShare
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return $this->mapper->find($id);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->handleException($e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(int $roomId, int $shareType, string $shareWith, int $permission): RoomShare
|
||||||
|
{
|
||||||
|
$roomShare = new RoomShare();
|
||||||
|
|
||||||
|
$roomShare->setRoomId($roomId);
|
||||||
|
$roomShare->setShareType($shareType);
|
||||||
|
$roomShare->setShareWith($shareWith);
|
||||||
|
$roomShare->setPermission($permission);
|
||||||
|
|
||||||
|
return $this->mapper->insert($roomShare);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(int $id, int $roomId, int $shareType, string $shareWith, int $permission): RoomShare
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$roomShare = $this->mapper->find($id);
|
||||||
|
|
||||||
|
$roomShare->setRoomId($roomId);
|
||||||
|
$roomShare->setShareType($shareType);
|
||||||
|
$roomShare->setShareWith($shareWith);
|
||||||
|
$roomShare->setPermission($permission);
|
||||||
|
|
||||||
|
return $this->mapper->update($roomShare);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->handleException($e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(int $id): RoomShare
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$roomShare = $this->mapper->find($id);
|
||||||
|
$this->mapper->delete($roomShare);
|
||||||
|
|
||||||
|
return $roomShare;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->handleException($e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,121 @@
|
||||||
|
<?php
|
||||||
|
namespace OCA\BigBlueButton\Tests\Controller;
|
||||||
|
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use OCP\IRequest;
|
||||||
|
use OCA\BigBlueButton\Service\RoomService;
|
||||||
|
use OCA\BigBlueButton\Controller\RoomShareController;
|
||||||
|
use OCA\BigBlueButton\Db\Room;
|
||||||
|
use OCA\BigBlueButton\Db\RoomShare;
|
||||||
|
use OCA\BigBlueButton\Service\RoomShareNotFound;
|
||||||
|
use OCA\BigBlueButton\Service\RoomShareService;
|
||||||
|
use OCP\AppFramework\Http;
|
||||||
|
use OCP\IUserManager;
|
||||||
|
|
||||||
|
class RoomShareControllerTest extends TestCase
|
||||||
|
{
|
||||||
|
private $request;
|
||||||
|
private $service;
|
||||||
|
private $roomService;
|
||||||
|
private $userManager;
|
||||||
|
private $controller;
|
||||||
|
|
||||||
|
public function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
$this->request = $this->createMock(IRequest::class);
|
||||||
|
$this->service = $this->createMock(RoomShareService::class);
|
||||||
|
$this->userManager = $this->createMock(IUserManager::class);
|
||||||
|
$this->roomService = $this->createMock(RoomService::class);
|
||||||
|
|
||||||
|
$this->controller = new RoomShareController(
|
||||||
|
'bbb',
|
||||||
|
$this->request,
|
||||||
|
$this->service,
|
||||||
|
$this->userManager,
|
||||||
|
$this->roomService,
|
||||||
|
'user_foo'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIndexWithoutRoomId()
|
||||||
|
{
|
||||||
|
$response = $this->controller->index();
|
||||||
|
|
||||||
|
$this->assertEquals(Http::STATUS_BAD_REQUEST, $response->getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIndexWithoutPermission()
|
||||||
|
{
|
||||||
|
$this->request
|
||||||
|
->expects($this->once())
|
||||||
|
->method('getParam')
|
||||||
|
->with('id')
|
||||||
|
->willReturn(1234);
|
||||||
|
|
||||||
|
$this->roomService
|
||||||
|
->expects($this->once())
|
||||||
|
->method('find')
|
||||||
|
->will($this->throwException(new RoomShareNotFound));
|
||||||
|
|
||||||
|
$response = $this->controller->index();
|
||||||
|
|
||||||
|
$this->assertEquals(Http::STATUS_FORBIDDEN, $response->getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIndexWithoutShares()
|
||||||
|
{
|
||||||
|
$roomId = 1234;
|
||||||
|
$this->request
|
||||||
|
->expects($this->once())
|
||||||
|
->method('getParam')
|
||||||
|
->with('id')
|
||||||
|
->willReturn($roomId);
|
||||||
|
|
||||||
|
$this->roomService
|
||||||
|
->expects($this->once())
|
||||||
|
->method('find')
|
||||||
|
->willReturn(new Room());
|
||||||
|
|
||||||
|
$this->service
|
||||||
|
->expects($this->once())
|
||||||
|
->method('findAll')
|
||||||
|
->with($roomId)
|
||||||
|
->willReturn([]);
|
||||||
|
|
||||||
|
$response = $this->controller->index();
|
||||||
|
|
||||||
|
$this->assertEquals(Http::STATUS_OK, $response->getStatus());
|
||||||
|
$this->assertEquals([], $response->getData());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIndexWithShares()
|
||||||
|
{
|
||||||
|
$roomId = 1234;
|
||||||
|
$this->request
|
||||||
|
->expects($this->once())
|
||||||
|
->method('getParam')
|
||||||
|
->with('id')
|
||||||
|
->willReturn($roomId);
|
||||||
|
|
||||||
|
$this->roomService
|
||||||
|
->expects($this->once())
|
||||||
|
->method('find')
|
||||||
|
->willReturn(new Room());
|
||||||
|
|
||||||
|
$this->service
|
||||||
|
->expects($this->once())
|
||||||
|
->method('findAll')
|
||||||
|
->with($roomId)
|
||||||
|
->willReturn([
|
||||||
|
new RoomShare()
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = $this->controller->index();
|
||||||
|
|
||||||
|
$this->assertEquals(Http::STATUS_OK, $response->getStatus());
|
||||||
|
$this->assertCount(1, $response->getData());
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,9 @@
|
||||||
import axios from '@nextcloud/axios';
|
import axios from '@nextcloud/axios';
|
||||||
|
|
||||||
|
export enum ShareType { User, Group };
|
||||||
|
|
||||||
|
export enum Permission { Admin, Moderator, User };
|
||||||
|
|
||||||
export enum Access {
|
export enum Access {
|
||||||
Public = 'public',
|
Public = 'public',
|
||||||
Password = 'password',
|
Password = 'password',
|
||||||
|
@ -19,6 +23,15 @@ export interface Room {
|
||||||
password?: string;
|
password?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface RoomShare {
|
||||||
|
id: number;
|
||||||
|
roomId: number;
|
||||||
|
shareType: ShareType;
|
||||||
|
shareWith: string;
|
||||||
|
shareWithDisplayName?: string;
|
||||||
|
permission: Permission;
|
||||||
|
}
|
||||||
|
|
||||||
export type Recording = {
|
export type Recording = {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -32,6 +45,23 @@ export type Recording = {
|
||||||
meta: any;
|
meta: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ShareWith {
|
||||||
|
users: {
|
||||||
|
label: string;
|
||||||
|
value: {
|
||||||
|
shareType: ShareType;
|
||||||
|
shareWith: string;
|
||||||
|
};
|
||||||
|
}[];
|
||||||
|
groups: {
|
||||||
|
label: string;
|
||||||
|
value: {
|
||||||
|
shareType: ShareType;
|
||||||
|
shareWith: string;
|
||||||
|
};
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
class Api {
|
class Api {
|
||||||
public getUrl(endpoint: string): string {
|
public getUrl(endpoint: string): string {
|
||||||
return OC.generateUrl(`apps/bbb/${endpoint}`);
|
return OC.generateUrl(`apps/bbb/${endpoint}`);
|
||||||
|
@ -109,6 +139,66 @@ class Api {
|
||||||
|
|
||||||
return response.data;
|
return response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async getRoomShares(roomId: number): Promise<RoomShare[]> {
|
||||||
|
const response = await axios.get(this.getUrl('roomShares'), {
|
||||||
|
params: {
|
||||||
|
id: roomId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async createRoomShare(roomId: number, shareType: ShareType, shareWith: string, permission: Permission): Promise<RoomShare> {
|
||||||
|
const response = await axios.post(this.getUrl('roomShares'), {
|
||||||
|
roomId,
|
||||||
|
shareType,
|
||||||
|
shareWith,
|
||||||
|
permission,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async deleteRoomShare(id: number) {
|
||||||
|
const response = await axios.delete(this.getUrl(`roomShares/${id}`));
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getRecommendedShareWith(): Promise<ShareWith> {
|
||||||
|
const url = OC.linkToOCS('apps/files_sharing/api/v1', 1) + 'sharees_recommended';
|
||||||
|
const response = await axios.get(url, {
|
||||||
|
params: {
|
||||||
|
itemType: 'room',
|
||||||
|
format: 'json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
users: response.data.ocs.data.exact.users,
|
||||||
|
groups: response.data.ocs.data.exact.groups,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async searchShareWith(search = ''): Promise<ShareWith> {
|
||||||
|
const url = OC.linkToOCS('apps/files_sharing/api/v1', 1) + 'sharees';
|
||||||
|
const response = await axios.get(url, {
|
||||||
|
params: {
|
||||||
|
search,
|
||||||
|
shareType: [OC.Share.SHARE_TYPE_USER, OC.Share.SHARE_TYPE_GROUP],
|
||||||
|
itemType: 'room',
|
||||||
|
format: 'json',
|
||||||
|
lookup: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
users: response.data.ocs.data.users,
|
||||||
|
groups: response.data.ocs.data.groups,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const api = new Api();
|
export const api = new Api();
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
import { Access, Room } from './Api';
|
||||||
import Dialog from './Dialog';
|
import Dialog from './Dialog';
|
||||||
import { Room, Access } from './Api';
|
import ShareWith from './ShareWith';
|
||||||
import { SubmitInput } from './SubmitInput';
|
import { SubmitInput } from './SubmitInput';
|
||||||
|
|
||||||
const descriptions: { [key: string]: string } = {
|
const descriptions: { [key: string]: string } = {
|
||||||
|
@ -74,6 +75,15 @@ const EditRoomDialog: React.FC<Props> = ({ room, updateProperty }) => {
|
||||||
updateProperty('access', value);
|
updateProperty('access', value);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
|
<div className="bbb-form-element">
|
||||||
|
<label htmlFor={'bbb-moderator'}>
|
||||||
|
<h3>Moderator</h3>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<ShareWith room={room} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>{t('bbb', 'Miscellaneous')}</h3>
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<input id={`bbb-record-${room.id}`}
|
<input id={`bbb-record-${room.id}`}
|
||||||
|
|
|
@ -24,6 +24,8 @@ declare namespace OC {
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Share {
|
namespace Share {
|
||||||
|
const SHARE_TYPE_USER = 0;
|
||||||
|
const SHARE_TYPE_GROUP = 1;
|
||||||
const SHARE_TYPE_LINK = 3;
|
const SHARE_TYPE_LINK = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,7 +54,7 @@ declare namespace OC {
|
||||||
function requirePasswordConfirmation(cb: () => void): void;
|
function requirePasswordConfirmation(cb: () => void): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateUrl(url: string, parameters?: { [key: string]: string }, options?: EscapeOptions)
|
function generateUrl(url: string, parameters?: { [key: string]: string|number }, options?: EscapeOptions)
|
||||||
|
|
||||||
function linkToOCS(service: string, version: number): string;
|
function linkToOCS(service: string, version: number): string;
|
||||||
|
|
||||||
|
@ -71,6 +73,10 @@ declare namespace OC {
|
||||||
|
|
||||||
const currentUser: string;
|
const currentUser: string;
|
||||||
|
|
||||||
|
function getCurrentUser(): {uid: string; displayName: string}
|
||||||
|
|
||||||
|
const requestToken: string;
|
||||||
|
|
||||||
const config: {
|
const config: {
|
||||||
blacklist_files_regex: string;
|
blacklist_files_regex: string;
|
||||||
enable_avatars: boolean;
|
enable_avatars: boolean;
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
.bbb-shareWith {
|
||||||
|
margin: 1em 0;
|
||||||
|
|
||||||
|
&__item {
|
||||||
|
height: 44px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--color-background-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatardiv {
|
||||||
|
height: 32px;
|
||||||
|
width: 32px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
height: 44px;
|
||||||
|
width: 44px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__label {
|
||||||
|
padding: 0 1em;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bbb-selection-container {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bbb-selection {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
background-color: #ffffff;
|
||||||
|
border: 1px solid var(--color-border-dark);
|
||||||
|
max-height: 88px;
|
||||||
|
overflow: auto;
|
||||||
|
box-shadow: 0 5px 10px -5px var(--color-box-shadow);
|
||||||
|
|
||||||
|
li {
|
||||||
|
padding: 0 1em;
|
||||||
|
cursor: pointer;
|
||||||
|
line-height: 44px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--color-background-hover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,129 @@
|
||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { api, ShareWith, ShareType, RoomShare, Room, Permission } from './Api';
|
||||||
|
import './ShareWith.scss';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
room: Room;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SearchInput: React.FC<Props> = ({ room }) => {
|
||||||
|
const [search, setSearch] = useState<string>('');
|
||||||
|
const [hasFocus, setFocus] = useState<boolean>(false);
|
||||||
|
const [recommendations, setRecommendations] = useState<ShareWith>();
|
||||||
|
const [searchResults, setSearchResults] = useState<ShareWith>();
|
||||||
|
const [shares, setShares] = useState<RoomShare[]>();
|
||||||
|
|
||||||
|
const userShares = shares ? shares.filter(share => share.shareType === ShareType.User).map(share => share.shareWith) : [];
|
||||||
|
const groupShares = shares ? shares.filter(share => share.shareType === ShareType.Group).map(share => share.shareWith) : [];
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
api.getRoomShares(room.id).then(roomShares => {
|
||||||
|
setShares(roomShares);
|
||||||
|
}).catch(err => {
|
||||||
|
console.warn('Could not load room shares.', err);
|
||||||
|
|
||||||
|
setShares([]);
|
||||||
|
});
|
||||||
|
}, [room.id]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
api.searchShareWith(search).then(result => {
|
||||||
|
setSearchResults(result);
|
||||||
|
});
|
||||||
|
}, [search]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
api.getRecommendedShareWith().then(result => setRecommendations(result));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
async function addRoomShare(shareWith: string, shareType: number, displayName: string) {
|
||||||
|
const roomShare = await api.createRoomShare(room.id, shareType, shareWith, Permission.Moderator);
|
||||||
|
|
||||||
|
roomShare.shareWithDisplayName = displayName;
|
||||||
|
|
||||||
|
setShares([...(shares || []), roomShare]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteRoomShare(id: number) {
|
||||||
|
await api.deleteRoomShare(id);
|
||||||
|
|
||||||
|
setShares(shares?.filter(share => share.id !== id));
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderSearchResults(options: ShareWith) {
|
||||||
|
return (
|
||||||
|
<ul className="bbb-selection">
|
||||||
|
{[
|
||||||
|
...options.users.filter(user => !userShares.includes(user.value.shareWith)),
|
||||||
|
...options.groups.filter(group => !groupShares.includes(group.value.shareWith)),
|
||||||
|
].map(option => {
|
||||||
|
return (<li key={option.value.shareWith} onClick={() => addRoomShare(option.value.shareWith, option.value.shareType, option.label)}>
|
||||||
|
{option.label}{option.value.shareType === ShareType.Group ? ` (${t('bbb', 'Group')})` : ''}
|
||||||
|
</li>);
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderShares(shares: RoomShare[]) {
|
||||||
|
const currentUser = OC.getCurrentUser();
|
||||||
|
const ownShare = {
|
||||||
|
id: -1,
|
||||||
|
roomId: room.id,
|
||||||
|
shareType: ShareType.User,
|
||||||
|
shareWith: currentUser.uid,
|
||||||
|
shareWithDisplayName: currentUser.displayName,
|
||||||
|
permission: Permission.Admin,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ul className="bbb-shareWith">
|
||||||
|
{[ownShare, ...shares].map(share => {
|
||||||
|
const avatarUrl = share.shareType === ShareType.User ? OC.generateUrl('/avatar/' + encodeURIComponent(share.shareWith) + '/' + 32, {
|
||||||
|
user: share.shareWith,
|
||||||
|
size: 32,
|
||||||
|
requesttoken: OC.requestToken,
|
||||||
|
}) : undefined;
|
||||||
|
const displayName = share.shareWithDisplayName || share.shareWith;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li key={share.id} className="bbb-shareWith__item">
|
||||||
|
<div className="avatardiv">
|
||||||
|
{avatarUrl && <img src={avatarUrl} alt={`Avatar from ${displayName}`} />}
|
||||||
|
</div>
|
||||||
|
<div className="bbb-shareWith__item__label">
|
||||||
|
<h5>{displayName}{share.shareType === ShareType.Group ? ` (${t('bbb', 'Group')})` : ''}</h5>
|
||||||
|
</div>
|
||||||
|
{share.id > -1 && <div className="bbb-shareWith__item__action">
|
||||||
|
<a className="icon icon-delete icon-visible"
|
||||||
|
onClick={ev => {ev.preventDefault(); deleteRoomShare(share.id);}}
|
||||||
|
title={t('bbb', 'Delete')} />
|
||||||
|
</div>}
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const loading = <><span className="icon icon-loading-small icon-visible"></span> {t('bbb', 'Loading')}</>;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{shares ? renderShares(shares) : loading}
|
||||||
|
|
||||||
|
<div className="bbb-selection-container">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={search}
|
||||||
|
onChange={ev => setSearch(ev.currentTarget.value)}
|
||||||
|
onFocus={() => setFocus(true)}
|
||||||
|
onBlur={() => setTimeout(() => setFocus(false), 100)}
|
||||||
|
placeholder={t('bbb', 'Name, group, ...')} />
|
||||||
|
{hasFocus && (searchResults ? renderSearchResults(searchResults) : (recommendations ? renderSearchResults(recommendations) : loading))}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SearchInput;
|
Loading…
Reference in New Issue