Skip to content

Commit a89bf72

Browse files
committed
feat: Add the possibility to create invitation links to circles
Signed-off-by: Kostiantyn Miakshyn <molodchick@gmail.com>
1 parent 32f07d8 commit a89bf72

File tree

7 files changed

+167
-51
lines changed

7 files changed

+167
-51
lines changed

appinfo/routes.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@
4040
['name' => 'Local#editSetting', 'url' => '/circles/{circleId}/setting', 'verb' => 'PUT'],
4141
['name' => 'Local#createInvitation', 'url' => '/circles/{circleId}/invitation', 'verb' => 'PUT'],
4242
['name' => 'Local#revokeInvitation', 'url' => '/circles/{circleId}/invitation', 'verb' => 'DELETE'],
43+
['name' => 'Local#getInvitation', 'url' => '/invitations/{invitationCode}', 'verb' => 'GET'],
44+
['name' => 'Local#joinInvitation', 'url' => '/invitations/{invitationCode}', 'verb' => 'POST'],
4345
['name' => 'Local#editConfig', 'url' => '/circles/{circleId}/config', 'verb' => 'PUT'],
4446
['name' => 'Local#link', 'url' => '/link/{circleId}/{singleId}', 'verb' => 'GET'],
4547

lib/Controller/LocalController.php

Lines changed: 80 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@
3232
use OCA\Circles\Service\SearchService;
3333
use OCA\Circles\Tools\Traits\TDeserialize;
3434
use OCA\Circles\Tools\Traits\TNCLogger;
35+
use OCP\AppFramework\Http;
36+
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
37+
use OCP\AppFramework\Http\Attribute\UserRateLimit;
3538
use OCP\AppFramework\Http\DataResponse;
3639
use OCP\AppFramework\OCS\OCSException;
3740
use OCP\AppFramework\OCSController;
@@ -591,13 +594,12 @@ public function link(string $circleId, string $singleId): DataResponse {
591594
}
592595

593596
/**
594-
* @NoAdminRequired
595-
*
596597
* @param string $circleId
597598
*
598599
* @return DataResponse
599600
* @throws OCSException
600601
*/
602+
#[NoAdminRequired]
601603
public function createInvitation(string $circleId): DataResponse {
602604
try {
603605
$this->setCurrentFederatedUser();
@@ -612,13 +614,12 @@ public function createInvitation(string $circleId): DataResponse {
612614
}
613615

614616
/**
615-
* @NoAdminRequired
616-
*
617617
* @param string $circleId
618618
*
619619
* @return DataResponse
620620
* @throws OCSException
621621
*/
622+
#[NoAdminRequired]
622623
public function revokeInvitation(string $circleId): DataResponse {
623624
try {
624625
$this->setCurrentFederatedUser();
@@ -632,6 +633,81 @@ public function revokeInvitation(string $circleId): DataResponse {
632633
}
633634
}
634635

636+
/**
637+
* @param string $invitationCode
638+
*
639+
* @return DataResponse
640+
* @throws OCSException
641+
*/
642+
#[NoAdminRequired]
643+
#[UserRateLimit(limit: 10, period: 3600)]
644+
public function getInvitation(string $invitationCode): DataResponse {
645+
try {
646+
$this->setCurrentFederatedUser();
647+
648+
$circleProbe = (new CircleProbe())
649+
->includeSystemCircles()
650+
->includeHiddenCircles()
651+
->filterByInvitationCode($invitationCode);
652+
653+
$circles = $this->circleService->getCircles($circleProbe);
654+
if (empty($circles)) {
655+
return new DataResponse([], Http::STATUS_NOT_FOUND);
656+
}
657+
$circle = reset($circles);
658+
659+
$membershipStatus = 'NOT_A_MEMBER';
660+
if ($circle->hasInitiator()) {
661+
if ($circle->getInitiator()->getLevel() > Member::LEVEL_NONE) {
662+
$membershipStatus = 'MEMBER';
663+
} elseif ($circle->getInitiator()->getStatus() === Member::STATUS_INVITED) {
664+
$membershipStatus = 'REQUESTED_MEMBERSHIP';
665+
}
666+
}
667+
668+
return new DataResponse([
669+
'circleId' => $circle->getSingleId(),
670+
'circleName' => $circle->getName(),
671+
'membershipStatus' => $membershipStatus,
672+
]);
673+
} catch (\Exception $e) {
674+
$this->e($e, ['circleId' => $invitationCode]);
675+
throw new OCSException($e->getMessage(), (int)$e->getCode(), $e);
676+
}
677+
}
678+
679+
/**
680+
* @param string $invitationCode
681+
*
682+
* @return DataResponse
683+
* @throws OCSException
684+
*/
685+
#[NoAdminRequired]
686+
#[UserRateLimit(limit: 10, period: 3600)]
687+
public function joinInvitation(string $invitationCode): DataResponse {
688+
try {
689+
$this->setCurrentFederatedUser();
690+
691+
$circleProbe = (new CircleProbe())
692+
->includeSystemCircles()
693+
->includeHiddenCircles()
694+
->filterByInvitationCode($invitationCode);
695+
696+
$circles = $this->circleService->getCircles($circleProbe);
697+
if (empty($circles)) {
698+
return new DataResponse([], Http::STATUS_NOT_FOUND);
699+
}
700+
$circle = reset($circles);
701+
702+
$result = $this->circleService->circleJoin($circle->getSingleId(), $invitationCode);
703+
704+
return new DataResponse($this->serializeArray($result));
705+
} catch (\Exception $e) {
706+
$this->e($e, ['circleId' => $invitationCode]);
707+
throw new OCSException($e->getMessage(), (int)$e->getCode(), $e);
708+
}
709+
}
710+
635711
/**
636712
* @return void
637713
* @throws FederatedUserException

lib/Db/CircleRequest.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,9 @@ public function getCircles(?IFederatedUser $initiator, CircleProbe $probe): arra
175175
if ($probe->hasFilterCircle()) {
176176
$qb->filterCircleDetails($probe->getFilterCircle());
177177
}
178+
if ($probe->hasInvitationCode()) {
179+
$qb->filterInvitationCode(CoreQueryBuilder::CIRCLE, $probe->getInvitationCode());
180+
}
178181
if ($probe->hasFilterRemoteInstance()) {
179182
$qb->limitToRemoteInstance(CoreQueryBuilder::CIRCLE, $probe->getFilterRemoteInstance(), false);
180183
}

lib/Db/CoreQueryBuilder.php

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -850,7 +850,6 @@ public function leftJoinCircleInvitation(string $alias, string $field = 'unique_
850850

851851
try {
852852
$aliasInvitation = $this->generateAlias($alias, self::INVITATION, $options);
853-
$getData = $this->getBool('getData', $options, false);
854853
} catch (RequestBuilderException $e) {
855854
return;
856855
}
@@ -1127,7 +1126,21 @@ public function limitToInitiator(
11271126
string $helperAlias = '',
11281127
): ICompositeExpression {
11291128
$this->leftJoinInitiator($alias, $user, $field, $helperAlias);
1130-
$where = $this->limitInitiatorVisibility($alias);
1129+
// var_dump('options', $this->options);
1130+
// var_dump('alias', $alias);
1131+
// var_dump('$field', $field);
1132+
// var_dump('$helperAlias', $helperAlias);
1133+
// var_dump('result', $this->get($alias.'.invitationCode', $this->options, ''));
1134+
// exit;
1135+
// var_dump('options', $this->options[$alias][self::OPTIONS]);
1136+
// var_dump('value', $this->get('filterInvitationCode', $this->options[$alias][self::OPTIONS], ''));
1137+
// exit;
1138+
// if ($this->get('filterInvitationCode', $this->options[$alias][self::OPTIONS], '')) {
1139+
// // for an invitation code request we don't need filter visibility
1140+
// $where = $this->expr()->andX($this->expr()->eq('1', '1'));
1141+
// } else {
1142+
$where = $this->limitInitiatorVisibility($alias);
1143+
// }
11311144

11321145
$aliasInitiator = $this->generateAlias($alias, self::INITIATOR, $options);
11331146
if ($this->getBool('getData', $options, false)) {
@@ -1331,6 +1344,12 @@ protected function limitInitiatorVisibility(string $alias): ICompositeExpression
13311344
$aliasMembershipCircle = $this->generateAlias($aliasMembership, self::CONFIG, $options);
13321345
$levelCheck = [$aliasMembership];
13331346

1347+
// no need to check anything, we are filtering by invitation code
1348+
$invitationCode = $this->get('filterInvitationCode', $options, '');
1349+
if ($invitationCode) {
1350+
return $this->expr()->andX($this->expr()->eq('1', '1'));
1351+
}
1352+
13341353
$directMember = '';
13351354
if ($this->getBool('initiatorDirectMember', $options, false)) {
13361355
$directMember = $this->generateAlias($alias, self::DIRECT_INITIATOR, $options);
@@ -1545,6 +1564,27 @@ public function limitToShareOwner(
15451564
}
15461565
}
15471566

1567+
/**
1568+
* filter circle by invitation code
1569+
*
1570+
* @param string $invitationCode
1571+
*/
1572+
public function filterInvitationCode(string $alias, string $invitationCode): void {
1573+
if ($this->getType() !== QueryBuilder::SELECT) {
1574+
return;
1575+
}
1576+
1577+
try {
1578+
$aliasInvitation = $this->generateAlias($alias, self::INVITATION, $options);
1579+
} catch (RequestBuilderException $e) {
1580+
return;
1581+
}
1582+
1583+
$expr = $this->expr();
1584+
$this->andWhere(
1585+
$expr->eq($aliasInvitation . '.invitation_code', $this->createNamedParameter($invitationCode))
1586+
);
1587+
}
15481588

15491589
/**
15501590
* @param string $aliasMount

lib/FederatedItems/CircleJoin.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,8 @@ public function verify(FederatedEvent $event): void {
130130
$member->setInvitedBy($initiator->getInvitedBy());
131131
}
132132

133-
$this->manageMemberStatus($circle, $member);
133+
$invitationCode = $event->getParams()->g('invitationCode');
134+
$this->manageMemberStatus($circle, $member, $invitationCode);
134135

135136
$this->circleService->confirmCircleNotFull($circle);
136137

@@ -251,11 +252,12 @@ public function result(FederatedEvent $event, array $results): void {
251252
/**
252253
* @param Circle $circle
253254
* @param Member $member
255+
* @param string|null $invitationCode
254256
*
255257
* @throws FederatedItemBadRequestException
256258
* @throws RequestBuilderException
257259
*/
258-
private function manageMemberStatus(Circle $circle, Member $member) {
260+
private function manageMemberStatus(Circle $circle, Member $member, ?string $invitationCode = null) {
259261
try {
260262
$knownMember = $this->memberRequest->searchMember($member);
261263
if ($knownMember->getLevel() === Member::LEVEL_NONE) {
@@ -277,7 +279,8 @@ private function manageMemberStatus(Circle $circle, Member $member) {
277279

278280
throw new MemberAlreadyExistsException(StatusCode::$CIRCLE_JOIN[122], 122);
279281
} catch (MemberNotFoundException $e) {
280-
if (!$circle->isConfig(Circle::CFG_OPEN)) {
282+
$allowedToJoin = $invitationCode && $circle->getCircleInvitation()?->getInvitationCode() === $invitationCode;
283+
if (!$circle->isConfig(Circle::CFG_OPEN) && !$allowedToJoin) {
281284
throw new FederatedItemBadRequestException(StatusCode::$CIRCLE_JOIN[124], 124);
282285
}
283286

lib/Model/Probes/CircleProbe.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ class CircleProbe extends MemberProbe {
2323
private int $filter = Circle::CFG_SINGLE;
2424
private bool $includeNonVisible = false;
2525
private bool $visitSingleCircles = false;
26+
private ?string $invitationCode = null;
2627
private int $limitConfig = 0;
2728

2829
/**
@@ -331,6 +332,32 @@ public function isFiltered(int $config): bool {
331332
return (($this->filtered() & $config) !== 0);
332333
}
333334

335+
/**
336+
* Apply a filter on the invitation code
337+
*
338+
* @param string $invitationCode
339+
*
340+
* @return $this
341+
*/
342+
public function filterByInvitationCode(string $invitationCode): self {
343+
$this->invitationCode = str_replace('-', '', $invitationCode);
344+
345+
return $this;
346+
}
347+
348+
/**
349+
* @return bool
350+
*/
351+
public function hasInvitationCode(): bool {
352+
return ($this->invitationCode !== null);
353+
}
354+
355+
/**
356+
* @return string|null
357+
*/
358+
public function getInvitationCode(): ?string {
359+
return $this->invitationCode;
360+
}
334361

335362
/**
336363
* Return an array with includes as options
@@ -355,6 +382,7 @@ public function getAsOptions(): array {
355382
'filterBackendCircles' => $this->isIncluded(Circle::CFG_BACKEND),
356383
'filterSystemCircles' => $this->isIncluded(Circle::CFG_SYSTEM),
357384
'filterPersonalCircles' => $this->isIncluded(Circle::CFG_PERSONAL),
385+
'filterInvitationCode' => $this->invitationCode,
358386
],
359387
parent::getAsOptions()
360388
);

lib/Service/CircleService.php

Lines changed: 6 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -476,55 +476,19 @@ public function updateDescription(string $circleId, string $description): array
476476
* @throws UnknownRemoteException
477477
* @throws RequestBuilderException
478478
*/
479-
public function circleJoin(string $circleId): array {
479+
public function circleJoin(string $circleId, ?string $invitationCode = null): array {
480+
$invitationCode = $invitationCode ? str_replace('-', '', $invitationCode) : null;
480481
$this->federatedUserService->mustHaveCurrentUser();
481482

482483
$probe = new CircleProbe();
483484
$probe->includeNonVisibleCircles()
484485
->emulateVisitor();
485486

486-
$circle = $this->circleRequest->getCircle(
487-
$circleId,
488-
$this->federatedUserService->getCurrentUser(),
489-
$probe
490-
);
491-
492-
if (!$circle->getInitiator()->hasInvitedBy()) {
493-
$this->federatedUserService->setMemberPatron($circle->getInitiator());
487+
if ($invitationCode) {
488+
$probe->includeHiddenCircles()
489+
->filterByInvitationCode($invitationCode);
494490
}
495491

496-
$event = new FederatedEvent(CircleJoin::class);
497-
$event->setCircle($circle);
498-
499-
$this->federatedEventService->newEvent($event);
500-
501-
return $event->getOutcome();
502-
}
503-
504-
/**
505-
* @param string $circleId
506-
*
507-
* @return array
508-
* @throws CircleNotFoundException
509-
* @throws FederatedEventException
510-
* @throws FederatedItemException
511-
* @throws InitiatorNotConfirmedException
512-
* @throws InitiatorNotFoundException
513-
* @throws OwnerNotFoundException
514-
* @throws RemoteInstanceException
515-
* @throws RemoteNotFoundException
516-
* @throws RemoteResourceNotFoundException
517-
* @throws UnknownRemoteException
518-
* @throws RequestBuilderException
519-
*/
520-
public function circleJoinByInvitationCode(string $circleId, string $invitationCode): array {
521-
// fixme: implement
522-
$this->federatedUserService->mustHaveCurrentUser();
523-
524-
$probe = new CircleProbe();
525-
$probe->includeNonVisibleCircles()
526-
->emulateVisitor();
527-
528492
$circle = $this->circleRequest->getCircle(
529493
$circleId,
530494
$this->federatedUserService->getCurrentUser(),
@@ -536,14 +500,14 @@ public function circleJoinByInvitationCode(string $circleId, string $invitationC
536500
}
537501

538502
$event = new FederatedEvent(CircleJoin::class);
503+
$event->setParams(new SimpleDataStore(['invitationCode' => $invitationCode]));
539504
$event->setCircle($circle);
540505

541506
$this->federatedEventService->newEvent($event);
542507

543508
return $event->getOutcome();
544509
}
545510

546-
547511
/**
548512
* @param string $circleId
549513
* @param bool $force

0 commit comments

Comments
 (0)