feat: collaborate with circles

add circles as third share type

fix #61
pull/75/head
sualko 2020-08-29 13:20:28 +02:00
parent 2c5ff0dbb7
commit 2c75653329
17 changed files with 181 additions and 20 deletions

1
img/circles.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 57 57" width="64" height="64"><path d="M7.1 28.5A21.4 21.4 0 0 1 28.5 7.1m10.7 40A21.4 21.4 0 0 1 10 39M39.2 10A21.4 21.4 0 0 1 47 39.2" fill="none" stroke="#fff" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><circle cx="28.5" cy="7.1" r="6.5" fill="#fff" /><circle cx="39.2" cy="-10" r="6.5" transform="rotate(90)" fill="#fff" /><circle cx="39.2" cy="-47" r="6.5" transform="rotate(90)" fill="#fff" /></svg>

After

Width:  |  Height:  |  Size: 480 B

67
lib/CircleHelper.php Normal file
View File

@ -0,0 +1,67 @@
<?php
namespace OCA\BigBlueButton;
use OCA\BigBlueButton\AppInfo\Application;
use OCP\App\IAppManager;
class CircleHelper {
public const LEVEL_NONE = 0;
public const LEVEL_MEMBER = 1;
public const LEVEL_MODERATOR = 4;
public const LEVEL_ADMIN = 8;
public const LEVEL_OWNER = 9;
private $api;
/** @var Application */
private $app;
/** @var IAppManager */
private $appManager;
private $cache = [];
public function __construct(
Application $app,
IAppManager $appManager
) {
$this->app = $app;
$this->appManager = $appManager;
}
public function isInCircle(string $userId, string $circleId): bool {
return \in_array($circleId, $this->getCircleIds($userId));
}
public function getCircleIds(string $userId): array {
if (!\array_key_exists($userId, $this->cache)) {
$this->cache[$userId] = [];
$api = $this->getCircleAPI();
if ($api !== false) {
$circles = $api->listCircles(\OCA\Circles\Api\v1\Circles::CIRCLES_ALL, '', \OCA\Circles\Api\v1\Circles::LEVEL_MEMBER);
foreach ($circles as $circle) {
$this->cache[$userId][] = $circle->getUniqueId();
}
}
}
return $this->cache[$userId];
}
public function getCircleAPI() {
if ($this->api === null) {
if ($this->appManager->isEnabledForUser('circles') && class_exists('\OCA\Circles\Api\v1\Circles')) {
$container = $this->app->getContainer();
$this->api = $container->query(\OCA\Circles\Api\v1\Circles::class);
} else {
$this->api = false;
}
}
return $this->api;
}
}

View File

@ -5,6 +5,7 @@ namespace OCA\BigBlueButton\Controller;
use OCA\BigBlueButton\Service\RoomService;
use OCA\BigBlueButton\Permission;
use OCA\BigBlueButton\Db\Room;
use OCA\BigBlueButton\CircleHelper;
use OCP\IRequest;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
@ -25,6 +26,9 @@ class RoomController extends Controller {
/** @var Permission */
private $permission;
/** @var CircleHelper */
private $circleHelper;
/** @var string */
private $userId;
@ -37,6 +41,7 @@ class RoomController extends Controller {
IUserManager $userManager,
IGroupManager $groupManager,
Permission $permission,
CircleHelper $circleHelper,
$userId
) {
parent::__construct($appName, $request);
@ -44,6 +49,7 @@ class RoomController extends Controller {
$this->userManager = $userManager;
$this->groupManager = $groupManager;
$this->permission = $permission;
$this->circleHelper = $circleHelper;
$this->userId = $userId;
}
@ -53,8 +59,9 @@ class RoomController extends Controller {
public function index(): DataResponse {
$user = $this->userManager->get($this->userId);
$groupIds = $this->groupManager->getUserGroupIds($user);
$circleIds = $this->circleHelper->getCircleIds($this->userId);
return new DataResponse($this->service->findAll($this->userId, $groupIds));
return new DataResponse($this->service->findAll($this->userId, $groupIds, $circleIds));
}
/**

View File

@ -5,6 +5,7 @@ namespace OCA\BigBlueButton\Controller;
use OCA\BigBlueButton\Db\RoomShare;
use OCA\BigBlueButton\Service\RoomService;
use OCA\BigBlueButton\Service\RoomShareNotFound;
use OCA\BigBlueButton\CircleHelper;
use OCP\IRequest;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
@ -26,6 +27,9 @@ class RoomShareController extends Controller {
/** @var RoomService */
private $roomService;
/** @var CircleHelper */
private $circleHelper;
use Errors;
public function __construct(
@ -34,12 +38,14 @@ class RoomShareController extends Controller {
RoomShareService $service,
IUserManager $userManager,
RoomService $roomService,
CircleHelper $circleHelper,
$userId
) {
parent::__construct($appName, $request);
$this->service = $service;
$this->userManager = $userManager;
$this->roomService = $roomService;
$this->circleHelper = $circleHelper;
$this->userId = $userId;
}
@ -58,17 +64,38 @@ class RoomShareController extends Controller {
}
$roomShares = $this->service->findAll($roomId);
$shares = [];
$circleAPI = $this->circleHelper->getCircleAPI();
/** @var RoomShare $roomShare */
foreach ($roomShares as $roomShare) {
if ($roomShare->getShareType() === RoomShare::SHARE_TYPE_USER) {
$shareWithUser = $this->userManager->get($roomShare->getShareWith());
if ($shareWithUser !== null) {
$roomShare->setShareWithDisplayName($shareWithUser->getDisplayName());
}
if ($shareWithUser === null) {
continue;
}
return new DataResponse($roomShares);
$roomShare->setShareWithDisplayName($shareWithUser->getDisplayName());
} elseif ($roomShare->getShareType() === RoomShare::SHARE_TYPE_CIRCLE) {
if ($circleAPI === false) {
continue;
}
$circle = $circleAPI->detailsCircle($roomShare->getShareWith());
if ($circle === null) {
continue;
}
$roomShare->setShareWithDisplayName($circle->getName());
}
$shares[] = $roomShare;
}
return new DataResponse($shares);
}
/**

View File

@ -45,7 +45,7 @@ class RoomMapper extends QBMapper {
/**
* @return array<Room>
*/
public function findAll(string $userId, array $groupIds): array {
public function findAll(string $userId, array $groupIds, array $circleIds): array {
/* @var $qb IQueryBuilder */
$qb = $this->db->getQueryBuilder();
$qb->select('r.*')
@ -63,6 +63,11 @@ class RoomMapper extends QBMapper {
$qb->expr()->eq('s.permission', $qb->createNamedParameter(RoomShare::PERMISSION_ADMIN, IQueryBuilder::PARAM_INT)),
$qb->expr()->eq('s.share_type', $qb->createNamedParameter(RoomShare::SHARE_TYPE_GROUP, IQueryBuilder::PARAM_INT)),
$qb->expr()->in('s.share_with', $qb->createNamedParameter($groupIds, IQueryBuilder::PARAM_STR_ARRAY))
),
$qb->expr()->andX(
$qb->expr()->eq('s.permission', $qb->createNamedParameter(RoomShare::PERMISSION_ADMIN, IQueryBuilder::PARAM_INT)),
$qb->expr()->eq('s.share_type', $qb->createNamedParameter(RoomShare::SHARE_TYPE_CIRCLE, IQueryBuilder::PARAM_INT)),
$qb->expr()->in('s.share_with', $qb->createNamedParameter($circleIds, IQueryBuilder::PARAM_STR_ARRAY))
)
)
)

View File

@ -5,6 +5,7 @@ namespace OCA\BigBlueButton\Db;
use JsonSerializable;
use OCP\AppFramework\Db\Entity;
use OCP\Share\IShare;
/**
* @method int getRoomId()
@ -24,8 +25,9 @@ class RoomShare extends Entity implements JsonSerializable {
public const PERMISSION_MODERATOR = 1;
public const PERMISSION_USER = 2;
public const SHARE_TYPE_USER = 0;
public const SHARE_TYPE_GROUP = 1;
public const SHARE_TYPE_USER = IShare::TYPE_USER;
public const SHARE_TYPE_GROUP = IShare::TYPE_GROUP;
public const SHARE_TYPE_CIRCLE = IShare::TYPE_CIRCLE;
protected $roomId;
protected $shareType;

View File

@ -29,18 +29,23 @@ class Permission {
/** @var RoomShareService */
private $roomShareService;
/** @var CircleHelper */
private $circleHelper;
public function __construct(
IUserManager $userManager,
IGroupManager $groupManager,
RoomService $roomService,
RestrictionService $restrictionService,
RoomShareService $roomShareService
RoomShareService $roomShareService,
CircleHelper $circleHelper
) {
$this->userManager = $userManager;
$this->groupManager = $groupManager;
$this->roomService = $roomService;
$this->restrictionService = $restrictionService;
$this->roomShareService = $roomShareService;
$this->circleHelper = $circleHelper;
}
public function getRestriction(string $uid): Restriction {
@ -51,7 +56,7 @@ class Permission {
}
public function isAllowedToCreateRoom(string $uid) {
$numberOfCreatedRooms = count($this->roomService->findAll($uid, []));
$numberOfCreatedRooms = count($this->roomService->findAll($uid, [], []));
$restriction = $this->getRestriction($uid);
return $restriction->getMaxRooms() < 0 || $restriction->getMaxRooms() > $numberOfCreatedRooms;
@ -104,6 +109,10 @@ class Permission {
if ($this->groupManager->isInGroup($uid, $share->getShareWith())) {
return true;
}
} elseif ($share->getShareType() === RoomShare::SHARE_TYPE_CIRCLE) {
if ($this->circleHelper->isInCircle($uid, $share->getShareWith())) {
return true;
}
}
}

View File

@ -19,8 +19,8 @@ class RoomService {
$this->mapper = $mapper;
}
public function findAll(string $userId, array $groupIds): array {
return $this->mapper->findAll($userId, $groupIds);
public function findAll(string $userId, array $groupIds, array $circleIds): array {
return $this->mapper->findAll($userId, $groupIds, $circleIds);
}
private function handleException(Exception $e): void {

View File

@ -80,18 +80,20 @@ class RoomMapperTest extends TestCase {
$foreignRoom2 = $this->insert($this->getRandomString(), $this->getRandomString());
$foreignRoom3 = $this->insert($this->getRandomString(), $this->getRandomString());
$this->assertCount(1, $this->mapper->findAll($this->userId, []));
$this->assertCount(1, $this->mapper->findAll($this->userId, [], []));
$shares = [];
$shares[] = $this->insertShare($foreignRoom1->getId(), RoomShare::SHARE_TYPE_USER, $this->userId);
$shares[] = $this->insertShare($foreignRoom1->getId(), RoomShare::SHARE_TYPE_GROUP, 'foo bar');
$shares[] = $this->insertShare($foreignRoom2->getId(), RoomShare::SHARE_TYPE_GROUP, 'foo bar');
$shares[] = $this->insertShare($foreignRoom3->getId(), RoomShare::SHARE_TYPE_USER, $this->getRandomString());
$shares[] = $this->insertShare($foreignRoom3->getId(), RoomShare::SHARE_TYPE_CIRCLE, 'c1');
$shares[] = $this->insertShare($foreignRoom3->getId(), RoomShare::SHARE_TYPE_CIRCLE, 'c2');
$shares[] = $this->insertShare($foreignRoom3->getId(), RoomShare::SHARE_TYPE_USER, $this->userId, RoomShare::PERMISSION_MODERATOR);
$shares[] = $this->insertShare($foreignRoom3->getId(), RoomShare::SHARE_TYPE_GROUP, 'foo bar', RoomShare::PERMISSION_USER);
$rooms = $this->mapper->findAll($this->userId, ['foo bar']);
$this->assertCount(3, $rooms);
$rooms = $this->mapper->findAll($this->userId, ['foo bar'], ['c1']);
$this->assertCount(4, $rooms);
$this->mapper->delete($room);
$this->mapper->delete($foreignRoom1);

View File

@ -9,6 +9,7 @@ use OCA\BigBlueButton\Controller\RoomShareController;
use OCA\BigBlueButton\Db\Room;
use OCA\BigBlueButton\Db\RoomShare;
use OCA\BigBlueButton\Service\RoomShareService;
use OCA\BigBlueButton\CircleHelper;
use OCP\AppFramework\Http;
use OCP\IUserManager;
@ -16,6 +17,7 @@ class RoomShareControllerTest extends TestCase {
private $request;
private $service;
private $roomService;
private $circleHelper;
private $userManager;
private $controller;
@ -28,6 +30,7 @@ class RoomShareControllerTest extends TestCase {
$this->service = $this->createMock(RoomShareService::class);
$this->userManager = $this->createMock(IUserManager::class);
$this->roomService = $this->createMock(RoomService::class);
$this->circleHelper = $this->createMock(CircleHelper::class);
$this->controller = new RoomShareController(
'bbb',
@ -35,6 +38,7 @@ class RoomShareControllerTest extends TestCase {
$this->service,
$this->userManager,
$this->roomService,
$this->circleHelper,
$this->userId
);
}

View File

@ -9,6 +9,7 @@ use OCA\BigBlueButton\Permission;
use OCA\BigBlueButton\Service\RoomService;
use OCA\BigBlueButton\Service\RoomShareService;
use OCA\BigBlueButton\Service\RestrictionService;
use OCA\BigBlueButton\CircleHelper;
use OCP\IUserManager;
use OCP\IGroupManager;
use OCP\IUser;
@ -34,6 +35,9 @@ class PermissionTest extends TestCase {
/** @var RestrictionService|MockObject */
private $restrictionService;
/** @var CircleHelper|MockObject */
private $circleHelper;
public function setUp(): void {
parent::setUp();
@ -52,12 +56,16 @@ class PermissionTest extends TestCase {
/** @var RoomShareService|MockObject */
$this->roomShareService = $this->createMock(RoomShareService::class);
/** @var CircleHelper|MockObject */
$this->circleHelper = $this->createMock(CircleHelper::class);
$this->permission = new Permission(
$this->userManager,
$this->groupManager,
$this->roomService,
$this->restrictionService,
$this->roomShareService
$this->roomShareService,
$this->circleHelper
);
}
@ -96,12 +104,13 @@ class PermissionTest extends TestCase {
$room = $this->createRoom(1, 'foo');
$this->roomShareService
->expects($this->exactly(4))
->expects($this->exactly(5))
->method('findAll')
->will($this->returnValueMap([
[1, [
$this->createRoomShare(RoomShare::SHARE_TYPE_USER, 'user', RoomShare::PERMISSION_ADMIN),
$this->createRoomShare(RoomShare::SHARE_TYPE_GROUP, 'group', RoomShare::PERMISSION_MODERATOR),
$this->createRoomShare(RoomShare::SHARE_TYPE_CIRCLE, 'circle', RoomShare::PERMISSION_USER),
]],
[2, []],
]));
@ -113,6 +122,13 @@ class PermissionTest extends TestCase {
['group_user', 'group', true],
]));
$this->circleHelper
->method('isInCircle')
->will($this->returnValueMap([
['bar', 'circle', false],
['circle_user', 'circle', true],
]));
$this->assertFalse($this->permission->isUser($room, null), 'Test guest user');
$this->assertFalse($this->permission->isUser($room, 'bar'), 'Test no matching share');
$this->assertFalse($this->permission->isUser($this->createRoom(2, 'foo'), 'bar'), 'Test empty shares');
@ -120,6 +136,7 @@ class PermissionTest extends TestCase {
$this->assertTrue($this->permission->isUser($room, 'foo'), 'Test room owner');
$this->assertTrue($this->permission->isUser($room, 'user'));
$this->assertTrue($this->permission->isUser($room, 'group_user'));
$this->assertTrue($this->permission->isUser($room, 'circle_user'));
}
private function createRoom(int $id, string $userId): Room {

View File

@ -1,6 +1,10 @@
import axios from '@nextcloud/axios';
export enum ShareType { User, Group };
export enum ShareType {
User = OC.Share.SHARE_TYPE_USER,
Group = OC.Share.SHARE_TYPE_GROUP,
Circle = OC.Share.SHARE_TYPE_CIRCLE
};
export enum Permission { Admin, Moderator, User };
@ -67,6 +71,7 @@ export interface ShareWithOption {
export interface ShareWith {
users: ShareWithOption[];
groups: ShareWithOption[];
circles: ShareWithOption[];
}
class Api {
@ -228,6 +233,7 @@ class Api {
return {
users: response.data.ocs.data.exact.users,
groups: response.data.ocs.data.exact.groups,
circles: response.data.ocs.data.exact.circles || [],
};
}
@ -248,6 +254,7 @@ class Api {
return {
users: [...data.users, ...data.exact.users],
groups: [...data.groups, ...data.exact.groups],
circles: [...(data.circles || []), ...(data.exact.circles || [])],
};
}
}

View File

@ -8,6 +8,7 @@ type Props = {
excluded?: {
groupIds?: string[];
userIds?: string[];
circleIds?: string[];
};
placeholder?: string;
}
@ -23,6 +24,7 @@ const ShareSelection: React.FC<Props> = (props) => {
const excluded = {
userIds: props.excluded?.userIds || [],
groupIds: props.excluded?.groupIds || [],
circleIds: props.excluded?.circleIds || [],
};
const placeholder = props.placeholder || t('bbb', 'Name, group, ...');
@ -59,6 +61,7 @@ const ShareSelection: React.FC<Props> = (props) => {
const results = options ? [
...options.users.filter(user => !excluded.userIds.includes(user.value.shareWith)),
...options.groups.filter(group => !excluded.groupIds.includes(group.value.shareWith)),
...options.circles.filter(circle => !excluded.circleIds.includes(circle.value.shareWith)),
] : [];
const renderOption = (option: ShareWithOption) => {

View File

@ -1,3 +1,8 @@
.icon-circle-white {
background-size: 50%;
background-image: url('../../img/circles.svg');
}
.bbb-shareWith {
margin: 1em 0;
@ -15,7 +20,8 @@
width: 32px;
overflow: hidden;
.icon-group-white {
.icon-group-white,
.icon-circle-white {
display: block;
height: 100%;
width: 100%;

View File

@ -18,6 +18,7 @@ const ShareWith: React.FC<Props> = ({ room, permission, shares: allShares, setSh
const sharedUserIds = shares ? shares.filter(share => share.shareType === ShareType.User).map(share => share.shareWith) : [];
const sharedGroupIds = shares ? shares.filter(share => share.shareType === ShareType.Group).map(share => share.shareWith) : [];
const sharedCircleIds = shares ? shares.filter(share => share.shareType === ShareType.Circle).map(share => share.shareWith) : [];
async function addRoomShare(shareWith: string, shareType: number, displayName: string, permission: Permission) {
const roomShare = await api.createRoomShare(room.id, shareType, shareWith, permission);
@ -80,6 +81,7 @@ const ShareWith: React.FC<Props> = ({ room, permission, shares: allShares, setSh
<div className="avatardiv">
{avatarUrl && <img src={avatarUrl} alt={`Avatar from ${displayName}`} />}
{share.shareType === ShareType.Group && <span className="icon-group-white"></span>}
{share.shareType === ShareType.Circle && <span className="icon-circle-white"></span>}
</div>
<div className="bbb-shareWith__item__label">
<h5>{displayName}
@ -115,7 +117,8 @@ const ShareWith: React.FC<Props> = ({ room, permission, shares: allShares, setSh
{isOwner ?
<ShareSelection
selectShare={(shareOption) => addRoomShare(shareOption.value.shareWith, shareOption.value.shareType, shareOption.label, permission)}
excluded={{userIds: sharedUserIds, groupIds: sharedGroupIds}}/> :
excluded={{userIds: sharedUserIds, groupIds: sharedGroupIds, circleIds: sharedCircleIds}}
shareType={[ShareType.User, ShareType.Group, ShareType.Circle]}/> :
<em>
<span className="icon icon-details icon-visible"></span> {t('bbb', 'You are not allowed to change this option, because this room is shared with you.')}
</em>

1
ts/Nextcloud.d.ts vendored
View File

@ -27,6 +27,7 @@ declare namespace OC {
const SHARE_TYPE_USER = 0;
const SHARE_TYPE_GROUP = 1;
const SHARE_TYPE_LINK = 3;
const SHARE_TYPE_CIRCLE = 7;
}
interface Plugin<T> {