mirror of https://github.com/sualko/cloud_bbb
parent
de834240dd
commit
dc30ec0e3e
|
@ -2,6 +2,7 @@
|
|||
return [
|
||||
'resources' => [
|
||||
'room' => ['url' => '/rooms'],
|
||||
'roomShare' => ['url' => '/roomShares'],
|
||||
'room_api' => ['url' => '/api/0.1/rooms'],
|
||||
],
|
||||
'routes' => [
|
||||
|
|
|
@ -10,8 +10,11 @@ use BigBlueButton\Core\Record;
|
|||
use BigBlueButton\Parameters\DeleteRecordingsParameters;
|
||||
use BigBlueButton\Parameters\IsMeetingRunningParameters;
|
||||
use OCA\BigBlueButton\Db\Room;
|
||||
use OCA\BigBlueButton\Db\RoomShare;
|
||||
use OCA\BigBlueButton\Service\RoomShareService;
|
||||
use OCP\IConfig;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\IGroupManager;
|
||||
|
||||
class API
|
||||
{
|
||||
|
@ -21,15 +24,25 @@ class API
|
|||
/** @var IURLGenerator */
|
||||
private $urlGenerator;
|
||||
|
||||
/** @var IGroupManager */
|
||||
private $groupManager;
|
||||
|
||||
/** @var RoomShareService */
|
||||
private $roomShareService;
|
||||
|
||||
/** @var BigBlueButton */
|
||||
private $server;
|
||||
|
||||
public function __construct(
|
||||
IConfig $config,
|
||||
IURLGenerator $urlGenerator
|
||||
IURLGenerator $urlGenerator,
|
||||
IGroupManager $groupManager,
|
||||
RoomShareService $roomShareService
|
||||
) {
|
||||
$this->config = $config;
|
||||
$this->urlGenerator = $urlGenerator;
|
||||
$this->groupManager = $groupManager;
|
||||
$this->roomShareService = $roomShareService;
|
||||
}
|
||||
|
||||
private function getServer()
|
||||
|
@ -51,7 +64,7 @@ class API
|
|||
*/
|
||||
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);
|
||||
|
||||
|
@ -68,6 +81,38 @@ class API
|
|||
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.
|
||||
*
|
||||
|
|
|
@ -3,21 +3,28 @@
|
|||
namespace OCA\BigBlueButton\Controller;
|
||||
|
||||
use Closure;
|
||||
|
||||
use Exception;
|
||||
use OCP\AppFramework\Http;
|
||||
use OCP\AppFramework\Http\DataResponse;
|
||||
|
||||
use OCA\BigBlueButton\Service\RoomNotFound;
|
||||
use OCA\BigBlueButton\Service\RoomShareNotFound;
|
||||
|
||||
trait Errors
|
||||
{
|
||||
protected function handleNotFound(Closure $callback): DataResponse
|
||||
{
|
||||
try {
|
||||
return new DataResponse($callback());
|
||||
} catch (RoomNotFound $e) {
|
||||
$return = $callback();
|
||||
return ($return instanceof DataResponse) ? $return : new DataResponse($return);
|
||||
} catch (Exception $e) {
|
||||
if ($e instanceof RoomNotFound ||
|
||||
$e instanceof RoomShareNotFound) {
|
||||
$message = ['message' => $e->getMessage()];
|
||||
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';
|
||||
|
||||
export enum ShareType { User, Group };
|
||||
|
||||
export enum Permission { Admin, Moderator, User };
|
||||
|
||||
export enum Access {
|
||||
Public = 'public',
|
||||
Password = 'password',
|
||||
|
@ -19,6 +23,15 @@ export interface Room {
|
|||
password?: string;
|
||||
}
|
||||
|
||||
export interface RoomShare {
|
||||
id: number;
|
||||
roomId: number;
|
||||
shareType: ShareType;
|
||||
shareWith: string;
|
||||
shareWithDisplayName?: string;
|
||||
permission: Permission;
|
||||
}
|
||||
|
||||
export type Recording = {
|
||||
id: string;
|
||||
name: string;
|
||||
|
@ -32,6 +45,23 @@ export type Recording = {
|
|||
meta: any;
|
||||
}
|
||||
|
||||
export interface ShareWith {
|
||||
users: {
|
||||
label: string;
|
||||
value: {
|
||||
shareType: ShareType;
|
||||
shareWith: string;
|
||||
};
|
||||
}[];
|
||||
groups: {
|
||||
label: string;
|
||||
value: {
|
||||
shareType: ShareType;
|
||||
shareWith: string;
|
||||
};
|
||||
}[];
|
||||
}
|
||||
|
||||
class Api {
|
||||
public getUrl(endpoint: string): string {
|
||||
return OC.generateUrl(`apps/bbb/${endpoint}`);
|
||||
|
@ -109,6 +139,66 @@ class Api {
|
|||
|
||||
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();
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React, { useState } from 'react';
|
||||
import { Access, Room } from './Api';
|
||||
import Dialog from './Dialog';
|
||||
import { Room, Access } from './Api';
|
||||
import ShareWith from './ShareWith';
|
||||
import { SubmitInput } from './SubmitInput';
|
||||
|
||||
const descriptions: { [key: string]: string } = {
|
||||
|
@ -74,6 +75,15 @@ const EditRoomDialog: React.FC<Props> = ({ room, updateProperty }) => {
|
|||
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>
|
||||
<input id={`bbb-record-${room.id}`}
|
||||
|
|
|
@ -24,6 +24,8 @@ declare namespace OC {
|
|||
}
|
||||
|
||||
namespace Share {
|
||||
const SHARE_TYPE_USER = 0;
|
||||
const SHARE_TYPE_GROUP = 1;
|
||||
const SHARE_TYPE_LINK = 3;
|
||||
}
|
||||
|
||||
|
@ -52,7 +54,7 @@ declare namespace OC {
|
|||
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;
|
||||
|
||||
|
@ -71,6 +73,10 @@ declare namespace OC {
|
|||
|
||||
const currentUser: string;
|
||||
|
||||
function getCurrentUser(): {uid: string; displayName: string}
|
||||
|
||||
const requestToken: string;
|
||||
|
||||
const config: {
|
||||
blacklist_files_regex: string;
|
||||
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