From 9c24a8e9d08968118803c6effe4adc11298d37ee Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Mon, 26 Feb 2024 18:04:28 +0100 Subject: [PATCH] feat(Contexts): API to create a new Context Signed-off-by: Arthur Schiwon --- appinfo/routes.php | 2 + lib/AppInfo/Application.php | 2 + lib/Controller/ContextController.php | 22 +++++ lib/Db/ContextNodeRelation.php | 39 +++++++++ lib/Db/ContextNodeRelationMapper.php | 18 +++++ lib/Db/Page.php | 32 ++++++++ lib/Db/PageContent.php | 34 ++++++++ lib/Db/PageContentMapper.php | 15 ++++ lib/Db/PageMapper.php | 15 ++++ lib/Service/ContextService.php | 117 ++++++++++++++++++++++++--- 10 files changed, 287 insertions(+), 9 deletions(-) create mode 100644 lib/Db/ContextNodeRelation.php create mode 100644 lib/Db/ContextNodeRelationMapper.php create mode 100644 lib/Db/Page.php create mode 100644 lib/Db/PageContent.php create mode 100644 lib/Db/PageContentMapper.php create mode 100644 lib/Db/PageMapper.php diff --git a/appinfo/routes.php b/appinfo/routes.php index 1e73a64dc..1246d6bc6 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -126,5 +126,7 @@ ['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'], + ] ]; diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 05047f29a..2120c66ae 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -29,6 +29,8 @@ class Application extends App implements IBootstrap { public const NODE_TYPE_TABLE = 0; public const NODE_TYPE_VIEW = 1; + public const OWNER_TYPE_USER = 0; + public function __construct() { parent::__construct(self::APP_ID); } diff --git a/lib/Controller/ContextController.php b/lib/Controller/ContextController.php index 5a09ab0b4..6e807841f 100644 --- a/lib/Controller/ContextController.php +++ b/lib/Controller/ContextController.php @@ -73,6 +73,28 @@ public function show(int $contextId): DataResponse { } } + /** + * [api v2] Create a new context and return it + * + * @NoAdminRequired + * + * @param string $name Name of the context + * @param string $iconName Material design icon name of the context + * @param string $description Descriptive text of the context + * @param array $nodes optional nodes to be connected to this context + * + * @return DataResponse|DataResponse + * + * 200: Tables returned + */ + public function create(string $name, string $iconName, string $description = '', array $nodes = []): DataResponse { + try { + return new DataResponse($this->contextService->create($name, $iconName, $description, $nodes, $this->userId, 0)->jsonSerialize()); + } catch (Exception $e) { + return $this->handleError($e); + } + } + /** * @param Context[] $contexts * @return array diff --git a/lib/Db/ContextNodeRelation.php b/lib/Db/ContextNodeRelation.php new file mode 100644 index 000000000..02b8e618d --- /dev/null +++ b/lib/Db/ContextNodeRelation.php @@ -0,0 +1,39 @@ +addType('id', 'integer'); + } + + public function jsonSerialize(): array { + return [ + 'id' => $this->getId(), + 'contextId' => $this->getContextId(), + 'nodeId' => $this->getNodeId(), + 'nodeType' => $this->getNodeType(), + 'permissions' => $this->getPermissions() + ]; + } +} diff --git a/lib/Db/ContextNodeRelationMapper.php b/lib/Db/ContextNodeRelationMapper.php new file mode 100644 index 000000000..5630b60ea --- /dev/null +++ b/lib/Db/ContextNodeRelationMapper.php @@ -0,0 +1,18 @@ + */ +class ContextNodeRelationMapper extends QBMapper { + protected string $table = 'tables_contexts_rel_context_node'; + + public function __construct(IDBConnection $db) { + parent::__construct($db, $this->table, ContextNodeRelation::class); + } + +} diff --git a/lib/Db/Page.php b/lib/Db/Page.php new file mode 100644 index 000000000..a7757fb1e --- /dev/null +++ b/lib/Db/Page.php @@ -0,0 +1,32 @@ +addType('id', 'integer'); + } + + public function jsonSerialize(): array { + return [ + 'id' => $this->getId(), + 'contextId' => $this->getContextId(), + 'pageType' => $this->getPageType(), + ]; + } +} diff --git a/lib/Db/PageContent.php b/lib/Db/PageContent.php new file mode 100644 index 000000000..45bb1e735 --- /dev/null +++ b/lib/Db/PageContent.php @@ -0,0 +1,34 @@ +addType('id', 'integer'); + } + + public function jsonSerialize(): array { + return [ + 'id' => $this->getId(), + 'pageId' => $this->getPageId(), + 'nodeRelId' => $this->getNodeRelId(), + 'order' => $this->getOrder(), + ]; + } +} diff --git a/lib/Db/PageContentMapper.php b/lib/Db/PageContentMapper.php new file mode 100644 index 000000000..38bed77ab --- /dev/null +++ b/lib/Db/PageContentMapper.php @@ -0,0 +1,15 @@ + */ +class PageContentMapper extends QBMapper { + protected string $table = 'tables_contexts_page_content'; + + public function __construct(IDBConnection $db) { + parent::__construct($db, $this->table, PageContent::class); + } +} diff --git a/lib/Db/PageMapper.php b/lib/Db/PageMapper.php new file mode 100644 index 000000000..6ade694f1 --- /dev/null +++ b/lib/Db/PageMapper.php @@ -0,0 +1,15 @@ + */ +class PageMapper extends QBMapper { + protected string $table = 'tables_contexts_page'; + + public function __construct(IDBConnection $db) { + parent::__construct($db, $this->table, Page::class); + } +} diff --git a/lib/Service/ContextService.php b/lib/Service/ContextService.php index 16790ebbd..db7fa122a 100644 --- a/lib/Service/ContextService.php +++ b/lib/Service/ContextService.php @@ -4,32 +4,52 @@ namespace OCA\Tables\Service; +use OCA\Tables\AppInfo\Application; use OCA\Tables\Db\Context; use OCA\Tables\Db\ContextMapper; +use OCA\Tables\Db\ContextNodeRelation; +use OCA\Tables\Db\ContextNodeRelationMapper; +use OCA\Tables\Db\Page; +use OCA\Tables\Db\PageContent; +use OCA\Tables\Db\PageContentMapper; +use OCA\Tables\Db\PageMapper; use OCA\Tables\Errors\InternalError; +use OCA\Tables\Errors\PermissionError; use OCP\DB\Exception; use Psr\Log\LoggerInterface; class ContextService { - private ContextMapper $mapper; + private ContextMapper $contextMapper; private bool $isCLI; private LoggerInterface $logger; + private ContextNodeRelationMapper $contextNodeRelMapper; + private PageMapper $pageMapper; + private PageContentMapper $pageContentMapper; + private PermissionsService $permissionsService; public function __construct( - ContextMapper $mapper, - LoggerInterface $logger, - bool $isCLI, + ContextMapper $contextMapper, + ContextNodeRelationMapper $contextNodeRelationMapper, + PageMapper $pageMapper, + PageContentMapper $pageContentMapper, + LoggerInterface $logger, + PermissionsService $permissionsService, + bool $isCLI, ) { - $this->mapper = $mapper; + $this->contextMapper = $contextMapper; $this->isCLI = $isCLI; $this->logger = $logger; + $this->contextNodeRelMapper = $contextNodeRelationMapper; + $this->pageMapper = $pageMapper; + $this->pageContentMapper = $pageContentMapper; + $this->permissionsService = $permissionsService; } /** - * @throws InternalError - * @throws Exception * @return Context[] + * @throws Exception + * @throws InternalError */ public function findAll(?string $userId): array { if ($userId !== null && trim($userId) === '') { @@ -40,7 +60,7 @@ public function findAll(?string $userId): array { $this->logger->warning($error); throw new InternalError($error); } - return $this->mapper->findAll($userId); + return $this->contextMapper->findAll($userId); } /** @@ -57,6 +77,85 @@ public function findById(int $id, ?string $userId): Context { throw new InternalError($error); } - return $this->mapper->findById($id, $userId); + return $this->contextMapper->findById($id, $userId); + } + + /** + * @throws Exception + */ + public function create(string $name, string $iconName, string $description, array $nodes, string $ownerId, int $ownerType): Context { + $context = new Context(); + $context->setName($name); + $context->setIcon($iconName); + $context->setDescription($description); + $context->setOwnerId($ownerId); + $context->setOwnerType($ownerType); + + $this->contextMapper->insert($context); + + if (!empty($nodes)) { + $context->resetUpdatedFields(); + $this->insertNodesFromArray($context, $nodes); + $this->insertPage($context); + } + + return $context; + } + + + protected function insertPage(Context $context): void { + $page = new Page(); + $page->setContextId($context->getId()); + $page->setPageType(Page::TYPE_STARTPAGE); + $this->pageMapper->insert($page); + + $addedPage = $page->jsonSerialize(); + + $i = 1; + foreach ($context->getNodes() as $node) { + $pageContent = new PageContent(); + $pageContent->setPageId($page->getId()); + $pageContent->setNodeRelId($node['id']); + $pageContent->setOrder(10 * $i++); + + $this->pageContentMapper->insert($pageContent); + + $addedPage['content'][$pageContent->getId()] = $pageContent->jsonSerialize(); + // the content is already embedded in the page + unset($addedPage['content'][$pageContent->getId()]['pageId']); + } + + $context->setPages($addedPage); + } + + protected function insertNodesFromArray(Context $context, array $nodes): void { + $addedNodes = []; + + $userId = $context->getOwnerType() === Application::OWNER_TYPE_USER ? $context->getOwnerId() : null; + foreach ($nodes as $node) { + $contextNodeRel = new ContextNodeRelation(); + $contextNodeRel->setContextId($context->getId()); + $contextNodeRel->setNodeId($node['id']); + $contextNodeRel->setNodeType($node['type']); + $contextNodeRel->setPermissions($node['permissions'] ?? 660); + + try { + if (!$this->permissionsService->canManageNodeById($node['type'], $node['id'], $userId)) { + throw new PermissionError(sprintf('Owner cannot manage node %d (type %d)', $node['id'], $node['type'])); + } + + $this->contextNodeRelMapper->insert($contextNodeRel); + $addedNodes[] = $contextNodeRel->jsonSerialize(); + } catch (Exception $e) { + $this->logger->warning('Could not add node {ntype}/{nid} to context {cid}, skipping.', [ + 'app' => Application::APP_ID, + 'ntype' => $node['type'], + 'nid' => $node['id'], + 'cid' => $context['id'], + 'exception' => $e, + ]); + } + } + $context->setNodes($addedNodes); } }