mirror of
https://github.com/nextcloud/tables.git
synced 2025-08-16 15:17:20 +00:00
feat: Add backend for table archive and favorite flag
Signed-off-by: Julius Härtl <jus@bitgrid.net>
This commit is contained in:
@ -17,7 +17,7 @@ return [
|
||||
// -> tables
|
||||
['name' => 'api1#index', 'url' => '/api/1/tables', 'verb' => 'GET'],
|
||||
['name' => 'api1#createTable', 'url' => '/api/1/tables', 'verb' => 'POST'],
|
||||
['name' => 'api1#updateTable', 'url' => '/api/1/tables/{tableId}', 'verb' => 'PUT'],
|
||||
['name' => 'api1#updateTable', 'url' => '/api/1/tables/{tableId}', 'verb' => 'PUT'], // needs archived
|
||||
['name' => 'api1#getTable', 'url' => '/api/1/tables/{tableId}', 'verb' => 'GET'],
|
||||
['name' => 'api1#deleteTable', 'url' => '/api/1/tables/{tableId}', 'verb' => 'DELETE'],
|
||||
// -> views
|
||||
@ -61,7 +61,7 @@ return [
|
||||
['name' => 'table#index', 'url' => '/table', 'verb' => 'GET'],
|
||||
['name' => 'table#show', 'url' => '/table/{id}', 'verb' => 'GET'],
|
||||
['name' => 'table#create', 'url' => '/table', 'verb' => 'POST'],
|
||||
['name' => 'table#update', 'url' => '/table/{id}', 'verb' => 'PUT'],
|
||||
['name' => 'table#update', 'url' => '/table/{id}', 'verb' => 'PUT'], // needs archived
|
||||
['name' => 'table#destroy', 'url' => '/table/{id}', 'verb' => 'DELETE'],
|
||||
|
||||
// view
|
||||
@ -115,7 +115,7 @@ return [
|
||||
['name' => 'ApiTables#index', 'url' => '/api/2/tables', 'verb' => 'GET'],
|
||||
['name' => 'ApiTables#show', 'url' => '/api/2/tables/{id}', 'verb' => 'GET'],
|
||||
['name' => 'ApiTables#create', 'url' => '/api/2/tables', 'verb' => 'POST'],
|
||||
['name' => 'ApiTables#update', 'url' => '/api/2/tables/{id}', 'verb' => 'PUT'],
|
||||
['name' => 'ApiTables#update', 'url' => '/api/2/tables/{id}', 'verb' => 'PUT'], // needs archived
|
||||
['name' => 'ApiTables#destroy', 'url' => '/api/2/tables/{id}', 'verb' => 'DELETE'],
|
||||
['name' => 'ApiTables#transfer', 'url' => '/api/2/tables/{id}/transfer', 'verb' => 'PUT'],
|
||||
|
||||
@ -125,5 +125,8 @@ return [
|
||||
['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' => 'ApiFavorite#create', 'url' => '/api/2/favorites/{nodeType}/{nodeId}', 'verb' => 'POST'],
|
||||
['name' => 'ApiFavorite#destroy', 'url' => '/api/2/favorites/{nodeType}/{nodeId}', 'verb' => 'DELETE'],
|
||||
]
|
||||
];
|
||||
|
80
lib/Controller/ApiFavoriteController.php
Normal file
80
lib/Controller/ApiFavoriteController.php
Normal file
@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace OCA\Tables\Controller;
|
||||
|
||||
use Exception;
|
||||
use OCA\Tables\Errors\InternalError;
|
||||
use OCA\Tables\Errors\NotFoundError;
|
||||
use OCA\Tables\Errors\PermissionError;
|
||||
use OCA\Tables\ResponseDefinitions;
|
||||
use OCA\Tables\Service\FavoritesService;
|
||||
use OCP\AppFramework\Http;
|
||||
use OCP\AppFramework\Http\DataResponse;
|
||||
use OCP\IL10N;
|
||||
use OCP\IRequest;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* @psalm-import-type TablesTable from ResponseDefinitions
|
||||
*/
|
||||
class ApiFavoriteController extends AOCSController {
|
||||
private FavoritesService $service;
|
||||
|
||||
public function __construct(
|
||||
IRequest $request,
|
||||
LoggerInterface $logger,
|
||||
FavoritesService $service,
|
||||
IL10N $n,
|
||||
string $userId) {
|
||||
parent::__construct($request, $logger, $n, $userId);
|
||||
$this->service = $service;
|
||||
}
|
||||
|
||||
/**
|
||||
* [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
|
||||
*
|
||||
* @return DataResponse<Http::STATUS_OK, TablesTable, array{}>|DataResponse<Http::STATUS_INTERNAL_SERVER_ERROR, array{message: string}, array{}>
|
||||
*
|
||||
* 200: Tables returned
|
||||
*/
|
||||
public function create(int $nodeType, int $nodeId): DataResponse {
|
||||
try {
|
||||
$this->service->addFavorite($nodeType, $nodeId);
|
||||
return new DataResponse(['ok']);
|
||||
} catch (InternalError|Exception $e) {
|
||||
return $this->handleError($e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* [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
|
||||
*/
|
||||
public function destroy(int $nodeType, int $nodeId): DataResponse {
|
||||
try {
|
||||
$this->service->removeFavorite($nodeType, $nodeId);
|
||||
return new DataResponse(['ok']);
|
||||
} catch (PermissionError $e) {
|
||||
return $this->handlePermissionError($e);
|
||||
} catch (InternalError $e) {
|
||||
return $this->handleError($e);
|
||||
} catch (NotFoundError $e) {
|
||||
return $this->handleNotFoundError($e);
|
||||
}
|
||||
}
|
||||
}
|
@ -106,9 +106,9 @@ class ApiTablesController extends AOCSController {
|
||||
* 403: No permissions
|
||||
* 404: Not found
|
||||
*/
|
||||
public function update(int $id, string $title = null, string $emoji = null): DataResponse {
|
||||
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, $this->userId)->jsonSerialize());
|
||||
return new DataResponse($this->service->update($id, $title, $emoji, $archived, $this->userId)->jsonSerialize());
|
||||
} catch (PermissionError $e) {
|
||||
return $this->handlePermissionError($e);
|
||||
} catch (InternalError $e) {
|
||||
|
@ -70,9 +70,9 @@ class TableController extends Controller {
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
*/
|
||||
public function update(int $id, string $title = null, string $emoji = null): DataResponse {
|
||||
return $this->handleError(function () use ($id, $title, $emoji) {
|
||||
return $this->service->update($id, $title, $emoji, $this->userId);
|
||||
public function update(int $id, string $title = null, string $emoji = null, ?bool $archived = null): DataResponse {
|
||||
return $this->handleError(function () use ($id, $title, $emoji, $archived) {
|
||||
return $this->service->update($id, $title, $emoji, $archived, $this->userId);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,8 @@ use OCP\AppFramework\Db\Entity;
|
||||
* @method setTitle(string $title)
|
||||
* @method getEmoji(): string
|
||||
* @method setEmoji(string $emoji)
|
||||
* @method getArchived(): bool
|
||||
* @method setArchived(bool $archived)
|
||||
* @method getOwnership(): string
|
||||
* @method setOwnership(string $ownership)
|
||||
* @method getOwnerDisplayName(): string
|
||||
@ -26,6 +28,8 @@ use OCP\AppFramework\Db\Entity;
|
||||
* @method setOnSharePermissions(array $onSharePermissions)
|
||||
* @method getHasShares(): bool
|
||||
* @method setHasShares(bool $hasShares)
|
||||
* @method getFavorite(): bool
|
||||
* @method setFavorite(bool $favorite)
|
||||
* @method getRowsCount(): int
|
||||
* @method setRowsCount(int $rowsCount)
|
||||
* @method getColumnsCount(): int
|
||||
@ -52,10 +56,12 @@ class Table extends Entity implements JsonSerializable {
|
||||
protected ?string $createdAt = null;
|
||||
protected ?string $lastEditBy = null;
|
||||
protected ?string $lastEditAt = null;
|
||||
protected bool $archived = false;
|
||||
protected ?bool $isShared = null;
|
||||
protected ?array $onSharePermissions = null;
|
||||
|
||||
protected ?bool $hasShares = false;
|
||||
protected ?bool $favorite = false;
|
||||
protected ?int $rowsCount = 0;
|
||||
protected ?int $columnsCount = 0;
|
||||
protected ?array $views = null;
|
||||
@ -63,6 +69,7 @@ class Table extends Entity implements JsonSerializable {
|
||||
|
||||
public function __construct() {
|
||||
$this->addType('id', 'integer');
|
||||
$this->addType('archived', 'boolean');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -79,7 +86,9 @@ class Table extends Entity implements JsonSerializable {
|
||||
'createdAt' => $this->createdAt ?: '',
|
||||
'lastEditBy' => $this->lastEditBy ?: '',
|
||||
'lastEditAt' => $this->lastEditAt ?: '',
|
||||
'archived' => $this->archived,
|
||||
'isShared' => !!$this->isShared,
|
||||
'favorite' => $this->favorite,
|
||||
'onSharePermissions' => $this->getSharePermissions(),
|
||||
'hasShares' => !!$this->hasShares,
|
||||
'rowsCount' => $this->rowsCount ?: 0,
|
||||
|
59
lib/Migration/Version000800Date20240222000000.php
Normal file
59
lib/Migration/Version000800Date20240222000000.php
Normal file
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
/** @noinspection PhpUnused */
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Tables\Migration;
|
||||
|
||||
use Closure;
|
||||
use OCP\DB\Exception;
|
||||
use OCP\DB\ISchemaWrapper;
|
||||
use OCP\DB\Types;
|
||||
use OCP\Migration\IOutput;
|
||||
use OCP\Migration\SimpleMigrationStep;
|
||||
|
||||
class Version000800Date20240222000000 extends SimpleMigrationStep {
|
||||
/**
|
||||
* @param IOutput $output
|
||||
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
|
||||
* @param array $options
|
||||
* @return null|ISchemaWrapper
|
||||
* @throws Exception
|
||||
*/
|
||||
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
|
||||
/** @var ISchemaWrapper $schema */
|
||||
$schema = $schemaClosure();
|
||||
|
||||
if ($schema->hasTable('tables_tables')) {
|
||||
$table = $schema->getTable('tables_tables');
|
||||
$table->addColumn('archived', Types::BOOLEAN, [
|
||||
'default' => false,
|
||||
'notnull' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
if (!$schema->hasTable('tables_favorites')) {
|
||||
$table = $schema->createTable('tables_favorites');
|
||||
$table->addColumn('id', Types::BIGINT, [
|
||||
'notnull' => true,
|
||||
'autoincrement' => true,
|
||||
'unsigned' => true,
|
||||
]);
|
||||
$table->addColumn('node_type', Types::SMALLINT, [
|
||||
'notnull' => true,
|
||||
]);
|
||||
$table->addColumn('node_id', Types::BIGINT, [
|
||||
'notnull' => true,
|
||||
]);
|
||||
$table->addColumn('user_id', Types::STRING, [
|
||||
'notnull' => true,
|
||||
'length' => 64,
|
||||
]);
|
||||
$table->setPrimaryKey(['id']);
|
||||
}
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
}
|
80
lib/Service/FavoritesService.php
Normal file
80
lib/Service/FavoritesService.php
Normal file
@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2024 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @author Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace OCA\Tables\Service;
|
||||
|
||||
use OCP\Cache\CappedMemoryCache;
|
||||
use OCP\IDBConnection;
|
||||
|
||||
class FavoritesService {
|
||||
|
||||
private CappedMemoryCache $cache;
|
||||
|
||||
public function __construct(private IDBConnection $connection, private ?string $userId) {
|
||||
$this->cache = new CappedMemoryCache();
|
||||
}
|
||||
|
||||
public function isFavorite(int $nodeType, int $id): bool {
|
||||
if ($cached = $this->cache->get($this->userId . '_' . $nodeType . '_' . $id)) {
|
||||
return $cached;
|
||||
}
|
||||
|
||||
// We still might run this multiple times
|
||||
|
||||
$qb = $this->connection->getQueryBuilder();
|
||||
$qb->select('*')
|
||||
->from('tables_favorites')
|
||||
->where($qb->expr()->eq('user_id', $qb->createNamedParameter($this->userId)));
|
||||
|
||||
$result = $qb->executeQuery();
|
||||
while ($row = $result->fetch()) {
|
||||
$this->cache->set($this->userId . '_' . $row['node_type'] . '_' . $row['node_id'], true);
|
||||
}
|
||||
|
||||
return $this->cache->get($this->userId . '_' . $nodeType . '_' . $id) ?? false;
|
||||
}
|
||||
|
||||
public function addFavorite(int $nodeType, int $id): void {
|
||||
$qb = $this->connection->getQueryBuilder();
|
||||
$qb->insert('tables_favorites')
|
||||
->values([
|
||||
'user_id' => $qb->createNamedParameter($this->userId),
|
||||
'node_type' => $qb->createNamedParameter($nodeType),
|
||||
'node_id' => $qb->createNamedParameter($id),
|
||||
]);
|
||||
$qb->executeStatement();
|
||||
$this->cache->set($this->userId . '_' . $nodeType . '_' . $id, true);
|
||||
}
|
||||
|
||||
public function removeFavorite(int $nodeType, int $id): void {
|
||||
$qb = $this->connection->getQueryBuilder();
|
||||
$qb->delete('tables_favorites')
|
||||
->where($qb->expr()->eq('user_id', $qb->createNamedParameter($this->userId)))
|
||||
->andWhere($qb->expr()->eq('node_type', $qb->createNamedParameter($nodeType)))
|
||||
->andWhere($qb->expr()->eq('node_id', $qb->createNamedParameter($id)));
|
||||
$qb->executeStatement();
|
||||
$this->cache->set($this->userId . '_' . $nodeType . '_' . $id, false);
|
||||
}
|
||||
|
||||
}
|
@ -6,6 +6,7 @@ namespace OCA\Tables\Service;
|
||||
|
||||
use DateTime;
|
||||
|
||||
use OCA\Tables\AppInfo\Application;
|
||||
use OCA\Tables\Db\Table;
|
||||
use OCA\Tables\Db\TableMapper;
|
||||
use OCA\Tables\Errors\InternalError;
|
||||
@ -38,6 +39,9 @@ class TableService extends SuperService {
|
||||
|
||||
protected UserHelper $userHelper;
|
||||
|
||||
protected FavoritesService $favoritesService;
|
||||
|
||||
|
||||
protected IL10N $l;
|
||||
|
||||
public function __construct(
|
||||
@ -51,6 +55,7 @@ class TableService extends SuperService {
|
||||
ViewService $viewService,
|
||||
ShareService $shareService,
|
||||
UserHelper $userHelper,
|
||||
FavoritesService $favoritesService,
|
||||
IL10N $l
|
||||
) {
|
||||
parent::__construct($logger, $userId, $permissionsService);
|
||||
@ -61,6 +66,7 @@ class TableService extends SuperService {
|
||||
$this->viewService = $viewService;
|
||||
$this->shareService = $shareService;
|
||||
$this->userHelper = $userHelper;
|
||||
$this->favoritesService = $favoritesService;
|
||||
$this->l = $l;
|
||||
}
|
||||
|
||||
@ -197,6 +203,11 @@ class TableService extends SuperService {
|
||||
$table->setViews($this->viewService->findAll($table));
|
||||
}
|
||||
|
||||
if ($this->favoritesService->isFavorite(Application::NODE_TYPE_TABLE, $table->getId())) {
|
||||
$table->setFavorite(true);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -420,7 +431,7 @@ class TableService extends SuperService {
|
||||
* @throws NotFoundError
|
||||
* @throws PermissionError
|
||||
*/
|
||||
public function update(int $id, ?string $title, ?string $emoji, ?string $userId = null): Table {
|
||||
public function update(int $id, ?string $title, ?string $emoji, ?bool $archived = null, ?string $userId = null): Table {
|
||||
$userId = $this->permissionsService->preCheckUserId($userId);
|
||||
|
||||
try {
|
||||
@ -445,6 +456,9 @@ class TableService extends SuperService {
|
||||
if ($emoji !== null) {
|
||||
$table->setEmoji($emoji);
|
||||
}
|
||||
if ($archived !== null) {
|
||||
$table->setArchived($archived);
|
||||
}
|
||||
$table->setLastEditBy($userId);
|
||||
$table->setLastEditAt($time->format('Y-m-d H:i:s'));
|
||||
try {
|
||||
|
Reference in New Issue
Block a user