Skip to content

Commit

Permalink
feat(Contexts): API endpoint for getting Contexts
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 21, 2024
1 parent 370379e commit e9a4741
Show file tree
Hide file tree
Showing 10 changed files with 597 additions and 66 deletions.
1 change: 1 addition & 0 deletions appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ Have a good time and manage whatever you want.
<command>OCA\Tables\Command\RemoveTable</command>
<command>OCA\Tables\Command\RenameTable</command>
<command>OCA\Tables\Command\ChangeOwnershipTable</command>
<command>OCA\Tables\Command\ListContexts</command>
<command>OCA\Tables\Command\Clean</command>
<command>OCA\Tables\Command\CleanLegacy</command>
<command>OCA\Tables\Command\TransferLegacyRows</command>
Expand Down
2 changes: 2 additions & 0 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -123,5 +123,7 @@
['name' => 'ApiColumns#createTextColumn', 'url' => '/api/2/columns/text', 'verb' => 'POST'],
['name' => 'ApiColumns#createSelectionColumn', 'url' => '/api/2/columns/selection', 'verb' => 'POST'],
['name' => 'ApiColumns#createDatetimeColumn', 'url' => '/api/2/columns/datetime', 'verb' => 'POST'],

['name' => 'Contexts#index', 'url' => '/api/3/contexts', 'verb' => 'GET'],
]
];
87 changes: 87 additions & 0 deletions lib/Command/ListContexts.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<?php

namespace OCA\Tables\Command;

use OC\Core\Command\Base;
use OCA\Tables\Errors\InternalError;
use OCA\Tables\Service\ContextService;
use OCP\DB\Exception;
use OCP\IConfig;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use function json_decode;
use function json_encode;

class ListContexts extends Base {
protected ContextService $contextService;
protected LoggerInterface $logger;
private IConfig $config;

public function __construct(
ContextService $contextService,
LoggerInterface $logger,
IConfig $config,
) {
parent::__construct();
$this->contextService = $contextService;
$this->logger = $logger;
$this->config = $config;
}

protected function configure(): void {
parent::configure();
$this
->setName('tables:contexts:list')
->setDescription('Get all contexts or contexts available to a specified user')
->addArgument(
'user-id',
InputArgument::OPTIONAL,
'User ID of the user'
)
;
}

protected function execute(InputInterface $input, OutputInterface $output): int {
$userId = trim($input->getArgument('user-id'));
if ($userId === '') {
$userId = null;
}

try {
$contexts = $this->contextService->findAll($userId);
} catch (InternalError|Exception $e) {
$output->writeln('Error while reading contexts from DB.');
$this->logger->warning('Following error occurred during executing occ command "{class}"',
[
'app' => 'tables',
'class' => self::class,
'exception' => $e,
]
);
if ($this->config->getSystemValueBool('debug', false)) {
$output->writeln(sprintf('<warning>%s</warning>', $e->getMessage()));
$output->writeln('<error>');
debug_print_backtrace();
$output->writeln('</error>');
}
return 1;
}

foreach ($contexts as $context) {
$contextArray = json_decode(json_encode($context), true);

$contextArray['ownerType'] = match ($contextArray['ownerType']) {
1 => 'group',
default => 'user',
};

$out = ['ID ' . $contextArray['id'] => $contextArray];
unset($out[$contextArray['id']]['id']);
$this->writeArrayInOutputFormat($input, $output, $out);
}

return 0;
}
}
66 changes: 66 additions & 0 deletions lib/Controller/ContextsController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php

namespace OCA\Tables\Controller;

use OCA\Tables\Db\Context;
use OCA\Tables\Errors\InternalError;
use OCA\Tables\ResponseDefinitions;
use OCA\Tables\Service\ContextService;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\DB\Exception;
use OCP\IL10N;
use OCP\IRequest;
use Psr\Log\LoggerInterface;

/**
* @psalm-import-type TablesContext from ResponseDefinitions
*/

class ContextsController extends AOCSController {
private ContextService $contextService;

public function __construct(
IRequest $request,
LoggerInterface $logger,
IL10N $n,
string $userId,
ContextService $contextService
) {
parent::__construct($request, $logger, $n, $userId);
$this->contextService = $contextService;
$this->userId = $userId;
}

/**
* [api v3] Get all contexts available to the requesting person
*
* Return an empty array if no contexts were found
*
* @return DataResponse<Http::STATUS_OK, TablesContext[], array{}>|DataResponse<Http::STATUS_INTERNAL_SERVER_ERROR, array{message: string}, array{}>
*
* 200: reporting in available contexts
*
* @NoAdminRequired
*/
public function index(): DataResponse {
try {
$contexts = $this->contextService->findAll($this->userId);
return new DataResponse($this->contextsToArray($contexts));
} catch (InternalError|Exception $e) {
return $this->handleError($e);
}
}

/**
* @param Context[] $contexts
* @return array
*/
protected function contextsToArray(array $contexts): array {
$result = [];
foreach ($contexts as $context) {
$result[] = $context->jsonSerialize();
}
return $result;
}
}
41 changes: 41 additions & 0 deletions lib/Db/Context.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

namespace OCA\Tables\Db;

use JsonSerializable;
use OCP\AppFramework\Db\Entity;

/**
* @method getName(): string
* @method setName(string $value): void
* @method getIcon(): string
* @method setIcon(string $value): void
* @method getDescription(): string
* @method setDescription(string $value): void
* @method getOwnerId(): string
* @method setOwnerId(string $value): void
* @method getOwnerType(): int
* @method setOwnerType(int $value): void
*/
class Context extends Entity implements JsonSerializable {
protected ?string $name = null;
protected ?string $icon = null;
protected ?string $description = null;
protected ?string $ownerId = null;
protected ?int $ownerType = null;

public function __construct() {
$this->addType('id', 'integer');
}

public function jsonSerialize(): array {
return [
'id' => $this->getId(),
'name' => $this->getName(),
'iconName' => $this->getIcon(),
'description' => $this->getDescription(),
'owner' => $this->getOwnerId(),
'ownerType' => $this->getOwnerType()
];
}
}
64 changes: 64 additions & 0 deletions lib/Db/ContextMapper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

namespace OCA\Tables\Db;

use OCA\Tables\Helper\UserHelper;
use OCP\AppFramework\Db\QBMapper;
use OCP\DB\Exception;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;

/** @template-extends QBMapper<Context> */
class ContextMapper extends QBMapper {
protected string $table = 'tables_contexts_context';
private UserHelper $userHelper;

public function __construct(IDBConnection $db, UserHelper $userHelper) {
$this->userHelper = $userHelper;
parent::__construct($db, $this->table, Context::class);
}

/**
* @return Context[]
* @throws Exception
*/
public function findAll(?string $userId = null): array {
$qb = $this->db->getQueryBuilder();
$qb->select('c.*')
->from($this->table, 'c');
if ($userId !== null) {
$sharedToConditions = $qb->expr()->orX();

// shared to user clause
$userShare = $qb->expr()->andX(
$qb->expr()->eq('s.receiver_type', $qb->createNamedParameter('user')),
$qb->expr()->eq('s.receiver', $qb->createNamedParameter($userId)),
);
$sharedToConditions->add($userShare);

// shared to group clause
$groupIDs = $this->userHelper->getGroupIdsForUser($userId);
if (!empty($groupIDs)) {
$groupShares = $qb->expr()->andX(
$qb->expr()->eq('s.receiver_type', $qb->createNamedParameter('group')),
$qb->expr()->in('s.receiver', $qb->createNamedParameter($groupIDs, IQueryBuilder::PARAM_STR_ARRAY)),
);
$sharedToConditions->add($groupShares);
}

// owned contexts + apply share conditions
$qb->leftJoin('c', 'tables_shares', 's', $qb->expr()->andX(
$qb->expr()->eq('c.id', 's.node_id'),
$qb->expr()->eq('s.node_type', $qb->createNamedParameter('context')),
$sharedToConditions,
));

$qb->where($qb->expr()->orX(
$qb->expr()->eq('owner_id', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)),
$qb->expr()->isNotNull('s.receiver'),
));
}

return $this->findEntities($qb);
}
}
9 changes: 9 additions & 0 deletions lib/ResponseDefinitions.php
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,15 @@
* errors_parsing_count: int,
* errors_count: int,
* }
*
* @psalm-type TablesContext = array{
* id: int,
* name: string,
* iconName: string,
* description: string,
* owner: string,
* ownerType: int,
* }
*/
class ResponseDefinitions {
}
43 changes: 43 additions & 0 deletions lib/Service/ContextService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

namespace OCA\Tables\Service;

use OCA\Tables\Db\Context;
use OCA\Tables\Db\ContextMapper;
use OCA\Tables\Errors\InternalError;
use OCP\DB\Exception;
use Psr\Log\LoggerInterface;

class ContextService {

private ContextMapper $mapper;
private bool $isCLI;
private LoggerInterface $logger;

public function __construct(
ContextMapper $mapper,
LoggerInterface $logger,
bool $isCLI,
) {
$this->mapper = $mapper;
$this->isCLI = $isCLI;
$this->logger = $logger;
}

/**
* @throws InternalError
* @throws Exception
* @return Context[]
*/
public function findAll(?string $userId): array {
if ($userId !== null && trim($userId) === '') {
$userId = null;
}
if ($userId === null && !$this->isCLI) {
$error = 'Try to set no user in context, but request is not allowed.';
$this->logger->warning($error);
throw new InternalError($error);
}
return $this->mapper->findAll($userId);
}
}
Loading

0 comments on commit e9a4741

Please sign in to comment.