Skip to content

Commit

Permalink
feat(Contexts): API to add and remove a node to a Context
Browse files Browse the repository at this point in the history
Signed-off-by: Arthur Schiwon <blizzz@arthur-schiwon.de>
  • Loading branch information
blizzz committed Feb 28, 2024
1 parent 9c24a8e commit f8a1591
Show file tree
Hide file tree
Showing 11 changed files with 325 additions and 13 deletions.
2 changes: 2 additions & 0 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@
['name' => 'Context#index', 'url' => '/api/2/contexts', 'verb' => 'GET'],
['name' => 'Context#show', 'url' => '/api/2/contexts/{contextId}', 'verb' => 'GET'],
['name' => 'Context#create', 'url' => '/api/2/contexts', 'verb' => 'POST'],
['name' => 'Context#addNode', 'url' => '/api/2/contexts/{contextId}/nodes', 'verb' => 'POST'],
['name' => 'Context#removeNode', 'url' => '/api/2/contexts/{contextId}/nodes/{nodeRelId}', 'verb' => 'DELETE'],

]
];
3 changes: 3 additions & 0 deletions lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use OCA\Tables\Listener\AnalyticsDatasourceListener;
use OCA\Tables\Listener\TablesReferenceListener;
use OCA\Tables\Listener\UserDeletedListener;
use OCA\Tables\Middleware\PermissionMiddleware;
use OCA\Tables\Reference\ContentReferenceProvider;
use OCA\Tables\Reference\LegacyReferenceProvider;
use OCA\Tables\Reference\ReferenceProvider;
Expand Down Expand Up @@ -62,6 +63,8 @@ public function register(IRegistrationContext $context): void {
}

$context->registerCapability(Capabilities::class);

$context->registerMiddleware(PermissionMiddleware::class);
}

public function boot(IBootContext $context): void {
Expand Down
10 changes: 10 additions & 0 deletions lib/Controller/AOCSController.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Exception;
use OCA\Tables\AppInfo\Application;
use OCA\Tables\Errors\BadRequestError;
use OCA\Tables\Errors\InternalError;
use OCA\Tables\Errors\NotFoundError;
use OCA\Tables\Errors\PermissionError;
Expand Down Expand Up @@ -59,4 +60,13 @@ protected function handleNotFoundError(NotFoundError $e): DataResponse {
return new DataResponse(['message' => $this->n->t('A not found error occurred. More details can be found in the logs. Please reach out to your administration.')], Http::STATUS_NOT_FOUND);
}

/**
* @param BadRequestError $e
* @return DataResponse<Http::STATUS_BAD_REQUEST, array{message: string}, array{}>
*/
protected function handleBadRequestError(BadRequestError $e): DataResponse {
$this->logger->warning('An bad request was encountered: ['. $e->getCode() . ']' . $e->getMessage());
return new DataResponse(['message' => $this->n->t('An error caused by an invalid request occurred. More details can be found in the logs. Please reach out to your administration.')], Http::STATUS_BAD_REQUEST);
}

}
46 changes: 46 additions & 0 deletions lib/Controller/ContextController.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@
namespace OCA\Tables\Controller;

use OCA\Tables\Db\Context;
use OCA\Tables\Errors\BadRequestError;
use OCA\Tables\Errors\InternalError;
use OCA\Tables\Errors\NotFoundError;
use OCA\Tables\ResponseDefinitions;
use OCA\Tables\Service\ContextService;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\DB\Exception;
Expand Down Expand Up @@ -95,6 +99,48 @@ public function create(string $name, string $iconName, string $description = '',
}
}

/**
* @NoAdminRequired
* @CanManageNode
*/
public function addNode(int $contextId, int $nodeId, int $nodeType, int $permissions, ?int $order = null): DataResponse {
try {
$rel = $this->contextService->addNodeToContextById($contextId, $nodeId, $nodeType, $permissions, $this->userId);
$this->contextService->addNodeRelToPage($rel, $order);
$context = $this->contextService->findById($rel->getContextId(), $this->userId);
return new DataResponse($context->jsonSerialize());
} catch (DoesNotExistException $e) {
return $this->handleNotFoundError(new NotFoundError($e->getMessage(), $e->getCode(), $e));
} catch (MultipleObjectsReturnedException|Exception|InternalError $e) {
return $this->handleError($e);
}
}

/**
* @NoAdminRequired
* @CanManageContext
*/
public function removeNode(int $contextId, int $nodeRelId): DataResponse {
// we could do without the contextId, however it is used by the Permission Middleware
// and also results in a more consistent endpoint url
try {
$context = $this->contextService->findById($contextId, $this->userId);
if (!isset($context->getNodes()[$nodeRelId])) {
return $this->handleBadRequestError(new BadRequestError('Node Relation ID not found in given Context'));
}
$nodeRelation = $this->contextService->removeNodeFromContextById($nodeRelId);
$this->contextService->removeNodeRelFromAllPages($nodeRelation);
$context = $this->contextService->findById($contextId, $this->userId);
return new DataResponse($context->jsonSerialize());
} catch (DoesNotExistException $e) {
$this->handleNotFoundError(new NotFoundError($e->getMessage(), $e->getCode(), $e));
} catch (MultipleObjectsReturnedException|Exception $e) {
$this->handleError($e);
}

return new DataResponse();
}

/**
* @param Context[] $contexts
* @return array
Expand Down
6 changes: 3 additions & 3 deletions lib/Db/ContextNodeRelation.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,16 @@
* @method setContextId(int $value): void
* @method getNodeId(): int
* @method setNodeId(int $value): void
* @method getNodeType(): string
* @method setNodeType(string $value): void
* @method getNodeType(): int
* @method setNodeType(int $value): void
* @method getPermissions(): int
* @method setPermissions(int $value): void
*/

class ContextNodeRelation extends Entity implements \JsonSerializable {
protected ?int $contextId = null;
protected ?int $nodeId = null;
protected ?string $nodeType = null;
protected ?int $nodeType = null;
protected ?int $permissions = null;

public function __construct() {
Expand Down
17 changes: 17 additions & 0 deletions lib/Db/ContextNodeRelationMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@

namespace OCA\Tables\Db;

use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\AppFramework\Db\QBMapper;
use OCP\DB\Exception;
use OCP\IDBConnection;

/** @template-extends QBMapper<ContextNodeRelation> */
Expand All @@ -15,4 +18,18 @@ public function __construct(IDBConnection $db) {
parent::__construct($db, $this->table, ContextNodeRelation::class);
}

/**
* @throws MultipleObjectsReturnedException
* @throws DoesNotExistException
* @throws Exception
*/
public function findById(int $nodeRelId): ContextNodeRelation {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->tableName)
->where($qb->expr()->eq('id', $qb->createNamedParameter($nodeRelId)));

$row = $this->findOneQuery($qb);
return $this->mapRowToEntity($row);
}
}
29 changes: 29 additions & 0 deletions lib/Db/PageContentMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace OCA\Tables\Db;

use OCP\AppFramework\Db\QBMapper;
use OCP\DB\Exception;
use OCP\IDBConnection;

/** @template-extends QBMapper<PageContent> */
Expand All @@ -12,4 +13,32 @@ class PageContentMapper extends QBMapper {
public function __construct(IDBConnection $db) {
parent::__construct($db, $this->table, PageContent::class);
}

/**
* @throws Exception
*/
public function findByPageAndNodeRelation(int $pageId, int $nodeRelId): ?PageContent {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->table)
->where($qb->expr()->andX(
$qb->expr()->eq('page_id', $qb->createNamedParameter($pageId)),
$qb->expr()->eq('node_rel_id', $qb->createNamedParameter($nodeRelId)),
));

$result = $qb->executeQuery();
$r = $result->fetch();
return $r ? $this->mapRowToEntity($r) : null;
}

public function findByNodeRelation(int $nodeRelId): array {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->table)
->where($qb->expr()->andX(
$qb->expr()->eq('node_rel_id', $qb->createNamedParameter($nodeRelId)),
));

return $this->findEntities($qb);
}
}
6 changes: 6 additions & 0 deletions lib/Errors/BadRequestError.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?php

namespace OCA\Tables\Errors;

class BadRequestError extends \Exception {
}
89 changes: 89 additions & 0 deletions lib/Middleware/PermissionMiddleware.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<?php

namespace OCA\Tables\Middleware;

use OCA\Tables\Errors\InternalError;
use OCA\Tables\Errors\PermissionError;
use OCA\Tables\Service\PermissionsService;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Middleware;
use OCP\AppFramework\Utility\IControllerMethodReflector;
use OCP\IRequest;

class PermissionMiddleware extends Middleware {
private IControllerMethodReflector $reflector;
private PermissionsService $permissionsService;
private ?string $userId;
private IRequest $request;

public function __construct(
IControllerMethodReflector $reflector,
PermissionsService $permissionsService,
IRequest $request,
?string $userId,
) {

$this->reflector = $reflector;
$this->permissionsService = $permissionsService;
$this->userId = $userId;
$this->request = $request;
}

/**
* @throws PermissionError
* @throws InternalError
*/
public function beforeController(Controller $controller, string $methodName): void {
$this->assertCanManageNode();
$this->assertCanManageContext();
}

/**
* @throws PermissionError
* @throws InternalError
*/
protected function assertCanManageNode(): void {
if ($this->reflector->hasAnnotation('CanManageNode')) {
$nodeId = $this->request->getParam('nodeId');
$nodeType = $this->request->getParam('nodeType');

if (!is_numeric($nodeId) || !is_numeric($nodeType)) {
throw new InternalError('Cannot identify node');
}

if ($this->userId === null) {
throw new PermissionError('User not authenticated');
}

if (!$this->permissionsService->canManageNodeById((int)$nodeType, (int)$nodeId, $this->userId)) {
throw new PermissionError(sprintf('User %s cannot manage node %d (type %d)',
$this->userId, (int)$nodeId, (int)$nodeType
));
}
}
}

/**
* @throws PermissionError
* @throws InternalError
*/
protected function assertCanManageContext(): void {
if ($this->reflector->hasAnnotation('CanManageContext')) {
$contextId = $this->request->getParam('contextId');

if (!is_numeric($contextId)) {
throw new InternalError('Cannot identify context');
}

if ($this->userId === null) {
throw new PermissionError('User not authenticated');
}

if (!$this->permissionsService->canManageContextById((int)$contextId, $this->userId)) {
throw new PermissionError(sprintf('User %s cannot manage context %d',
$this->userId, (int)$contextId
));
}
}
}
}
Loading

0 comments on commit f8a1591

Please sign in to comment.