Skip to content

Commit

Permalink
feat(Middleware): introduce InjectionMiddleware, with Table support f…
Browse files Browse the repository at this point in the history
…irst

- no more handcrafted Table instantiation and error handling on Controllers
- switching to Attributes

Signed-off-by: Arthur Schiwon <blizzz@arthur-schiwon.de>
  • Loading branch information
blizzz committed Mar 12, 2024
1 parent 8f7c6d8 commit 66f2d93
Show file tree
Hide file tree
Showing 4 changed files with 174 additions and 55 deletions.
17 changes: 17 additions & 0 deletions lib/Controller/AEnvironmentAwareOCSController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace OCA\Tables\Controller;

use OCA\Tables\Db\Table;

class AEnvironmentAwareOCSController extends AOCSController {
protected ?Table $table;

public function setTable(Table $table): void {
$this->table = $table;
}

public function getTable(): ?Table {
return $this->table;
}
}
91 changes: 36 additions & 55 deletions lib/Controller/ApiTablesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
use OCA\Tables\Errors\InternalError;
use OCA\Tables\Errors\NotFoundError;
use OCA\Tables\Errors\PermissionError;
use OCA\Tables\Middleware\Attribute\RequireTable;
use OCA\Tables\ResponseDefinitions;
use OCA\Tables\Service\TableService;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\DataResponse;
use OCP\IL10N;
use OCP\IRequest;
Expand All @@ -17,7 +19,7 @@
/**
* @psalm-import-type TablesTable from ResponseDefinitions
*/
class ApiTablesController extends AOCSController {
class ApiTablesController extends AEnvironmentAwareOCSController {
private TableService $service;

public function __construct(
Expand All @@ -33,12 +35,11 @@ public function __construct(
/**
* [api v2] Returns all Tables
*
* @NoAdminRequired
*
* @return DataResponse<Http::STATUS_OK, TablesTable[], array{}>|DataResponse<Http::STATUS_INTERNAL_SERVER_ERROR, array{message: string}, array{}>
*
* 200: Tables returned
*/
#[NoAdminRequired]
public function index(): DataResponse {
try {
return new DataResponse($this->service->formatTables($this->service->findAll($this->userId)));
Expand All @@ -50,32 +51,21 @@ public function index(): DataResponse {
/**
* [api v2] Get a table object
*
* @NoAdminRequired
*
* @param int $id Table ID
* @return DataResponse<Http::STATUS_OK, TablesTable, array{}>|DataResponse<Http::STATUS_FORBIDDEN|Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_NOT_FOUND, array{message: string}, array{}>
*
* 200: Table returned
* 403: No permissions
* 404: Not found
*/
public function show(int $id): DataResponse {
try {
return new DataResponse($this->service->find($id)->jsonSerialize());
} catch (PermissionError $e) {
return $this->handlePermissionError($e);
} catch (InternalError $e) {
return $this->handleError($e);
} catch (NotFoundError $e) {
return $this->handleNotFoundError($e);
}
#[NoAdminRequired]
#[RequireTable(enhance: true)]
public function show(): DataResponse {
return new DataResponse($this->getTable()->jsonSerialize());
}

/**
* [api v2] Create a new table and return it
*
* @NoAdminRequired
*
* @param string $title Title of the table
* @param string|null $emoji Emoji for the table
* @param string $template Template to use if wanted
Expand All @@ -84,6 +74,7 @@ public function show(int $id): DataResponse {
*
* 200: Tables returned
*/
#[NoAdminRequired]
public function create(string $title, ?string $emoji, string $template = 'custom'): DataResponse {
try {
return new DataResponse($this->service->create($title, $template, $emoji)->jsonSerialize());
Expand All @@ -95,9 +86,6 @@ public function create(string $title, ?string $emoji, string $template = 'custom
/**
* [api v2] Update tables properties
*
* @NoAdminRequired
*
* @param int $id Table ID
* @param string|null $title New table title
* @param string|null $emoji New table emoji
* @param bool $archived whether the table is archived
Expand All @@ -106,41 +94,36 @@ public function create(string $title, ?string $emoji, string $template = 'custom
* 200: Tables returned
* 403: No permissions
* 404: Not found
*
* @throws InternalError
* @throws NotFoundError
* @throws PermissionError
*/
public function update(int $id, ?string $title = null, ?string $emoji = null, ?bool $archived = null): DataResponse {
try {
return new DataResponse($this->service->update($id, $title, $emoji, $archived, $this->userId)->jsonSerialize());
} catch (PermissionError $e) {
return $this->handlePermissionError($e);
} catch (InternalError $e) {
return $this->handleError($e);
} catch (NotFoundError $e) {
return $this->handleNotFoundError($e);
}
#[NoAdminRequired]
#[RequireTable(enhance: true)]
public function update(?string $title = null, ?string $emoji = null, ?bool $archived = null): DataResponse {
// TODO: service class to accept Table instead of ID
return new DataResponse($this->service->update($this->getTable()->getId(), $title, $emoji, $archived, $this->userId)->jsonSerialize());
}

/**
* [api v2] Delete a table
*
* @NoAdminRequired
*
* @param int $id Table ID
* @return DataResponse<Http::STATUS_OK, TablesTable, array{}>|DataResponse<Http::STATUS_FORBIDDEN|Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_NOT_FOUND, array{message: string}, array{}>
*
* 200: Deleted table returned
* 403: No permissions
* 404: Not found
*
* @throws InternalError
* @throws NotFoundError
* @throws PermissionError
*/
public function destroy(int $id): DataResponse {
try {
return new DataResponse($this->service->delete($id)->jsonSerialize());
} catch (PermissionError $e) {
return $this->handlePermissionError($e);
} catch (InternalError $e) {
return $this->handleError($e);
} catch (NotFoundError $e) {
return $this->handleNotFoundError($e);
}
#[NoAdminRequired]
#[RequireTable(enhance: true)]
public function destroy(): DataResponse {
// TODO: service class to accept Table instead of ID
return new DataResponse($this->service->delete($this->getTable()->getId())->jsonSerialize());
}

/**
Expand All @@ -150,24 +133,22 @@ public function destroy(int $id): DataResponse {
*
* @NoAdminRequired
*
* @param int $id Table ID
* @param string $newOwnerUserId New user ID
*
* @return DataResponse<Http::STATUS_OK, TablesTable, array{}>|DataResponse<Http::STATUS_FORBIDDEN|Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_NOT_FOUND, array{message: string}, array{}>
*
* 200: Ownership changed
* 403: No permissions
* 404: Not found
*
* @throws InternalError
* @throws NotFoundError
* @throws PermissionError
*/
public function transfer(int $id, string $newOwnerUserId): DataResponse {
try {
return new DataResponse($this->service->setOwner($id, $newOwnerUserId)->jsonSerialize());
} catch (PermissionError $e) {
return $this->handlePermissionError($e);
} catch (InternalError $e) {
return $this->handleError($e);
} catch (NotFoundError $e) {
return $this->handleNotFoundError($e);
}
#[NoAdminRequired]
#[RequireTable(enhance: true)]
public function transfer(string $newOwnerUserId): DataResponse {
// TODO: service class to accept Table instead of ID
return new DataResponse($this->service->setOwner($this->getTable()->getId(), $newOwnerUserId)->jsonSerialize());
}
}
19 changes: 19 additions & 0 deletions lib/Middleware/Attribute/RequireTable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

declare(strict_types=1);

namespace OCA\Tables\Middleware\Attribute;

use Attribute;

#[Attribute(Attribute::TARGET_METHOD)]
class RequireTable {
public function __construct(
protected bool $enhance = false,
) {
}

public function enhance(): bool {
return $this->enhance;
}
}
102 changes: 102 additions & 0 deletions lib/Middleware/InjectionMiddleware.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<?php

namespace OCA\Tables\Middleware;

use InvalidArgumentException;
use OCA\Tables\Controller\AEnvironmentAwareOCSController;
use OCA\Tables\Errors\InternalError;
use OCA\Tables\Errors\NotFoundError;
use OCA\Tables\Errors\PermissionError;
use OCA\Tables\Middleware\Attribute\RequireTable;
use OCA\Tables\Service\TableService;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Http\RedirectResponse;
use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Middleware;
use OCP\AppFramework\OCS\OCSException;
use OCP\IL10N;
use OCP\IRequest;
use OCP\IURLGenerator;
use OCP\IUserSession;
use Psr\Log\LoggerInterface;
use ReflectionAttribute;
use ReflectionMethod;

class InjectionMiddleware extends Middleware {
public function __construct(
protected IRequest $request,
protected IURLGenerator $urlGenerator,
protected IUserSession $userSession,
protected LoggerInterface $logger,
protected IL10N $l,
protected TableService $tableService,
) {
}

/**
* @throws PermissionError
* @throws \ReflectionException
* @throws NotFoundError
* @throws InternalError
*/
public function beforeController($controller, $methodName): void {
if (!$controller instanceof AEnvironmentAwareOCSController) {
return;
}

$reflectionMethod = new ReflectionMethod($controller, $methodName);
$attributes = $reflectionMethod->getAttributes(RequireTable::class);
if (!empty($attributes)) {
/** @var ReflectionAttribute $attribute */
$attribute = current($attributes);
/** @var RequireTable $requireTableAttribute */
$requireTableAttribute = $attribute->newInstance();
$this->getTable($controller, $requireTableAttribute->enhance());
}
}

public function afterException($controller, $methodName, $exception): Response {
if ($exception instanceof InvalidArgumentException) {
throw new OCSException($exception->getMessage(), Http::STATUS_BAD_REQUEST, $exception);
}

$loggerOptions = [
'errorCode' => $exception->getCode(),
'errorMessage' => $exception->getMessage(),
'exception' => $exception,
];

if ($exception instanceof NotFoundError) {
$this->logger->warning('A not found error occurred: [{errorCode}] {errorMessage}', $loggerOptions);
return new DataResponse(['message' => $this->l->t('A not found error occurred. More details can be found in the logs. Please reach out to your administration.')], Http::STATUS_NOT_FOUND);
}
if ($exception instanceof InternalError) {
$this->logger->warning('An internal error or exception occurred: [{errorCode}] {errorMessage}', $loggerOptions);
return new DataResponse(['message' => $this->l->t('An unexpected error occurred. More details can be found in the logs. Please reach out to your administration.')], Http::STATUS_INTERNAL_SERVER_ERROR);
}
if ($exception instanceof PermissionError) {
$this->logger->warning('A permission error occurred: [{errorCode}] {errorMessage}', $loggerOptions);
return new DataResponse(['message' => $this->l->t('A permission error occurred. More details can be found in the logs. Please reach out to your administration.')], Http::STATUS_FORBIDDEN);
}

$this->logger->warning('A unknown error occurred: [{errorCode}] {errorMessage}', $loggerOptions);
return new RedirectResponse($this->urlGenerator->linkToDefaultPageUrl());
}

/**
* @throws NotFoundError
* @throws PermissionError
* @throws InternalError
*/
protected function getTable(AEnvironmentAwareOCSController $controller, bool $enhance): void {
$tableId = $this->request->getParam('id');
if ($tableId === null) {
throw new InvalidArgumentException('Missing table ID.');
}

$userId = $this->userSession->getUser()?->getUID();

$controller->setTable($this->tableService->find($tableId, !$enhance, $userId));
}
}

0 comments on commit 66f2d93

Please sign in to comment.