Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@
['name' => 'api1#deleteRowByView', 'url' => '/api/1/views/{viewId}/rows/{rowId}', 'verb' => 'DELETE'],
['name' => 'api1#updateRow', 'url' => '/api/1/rows/{rowId}', 'verb' => 'PUT'],
['name' => 'api1#deleteRow', 'url' => '/api/1/rows/{rowId}', 'verb' => 'DELETE'],
// -> relations
['name' => 'api1#getRelations', 'url' => '/api/1/relations/{columnId}/{rowId}', 'verb' => 'GET'],
['name' => 'api1#setRelations', 'url' => '/api/1/relations/{columnId}/{rowId}', 'verb' => 'PUT'],
// -> import
['name' => 'api1#importInTable', 'url' => '/api/1/import/table/{tableId}', 'verb' => 'POST'],
['name' => 'api1#importInView', 'url' => '/api/1/import/views/{viewId}', 'verb' => 'POST'],
Expand Down Expand Up @@ -135,6 +138,7 @@
['name' => 'ApiColumns#createSelectionColumn', 'url' => '/api/2/columns/selection', 'verb' => 'POST'],
['name' => 'ApiColumns#createDatetimeColumn', 'url' => '/api/2/columns/datetime', 'verb' => 'POST'],
['name' => 'ApiColumns#createUsergroupColumn', 'url' => '/api/2/columns/usergroup', 'verb' => 'POST'],
['name' => 'ApiColumns#createRelationColumn', 'url' => '/api/2/columns/relation', 'verb' => 'POST'],

['name' => 'ApiFavorite#create', 'url' => '/api/2/favorites/{nodeType}/{nodeId}', 'verb' => 'POST', 'requirements' => ['nodeType' => '(\d+)', 'nodeId' => '(\d+)']],
['name' => 'ApiFavorite#destroy', 'url' => '/api/2/favorites/{nodeType}/{nodeId}', 'verb' => 'DELETE', 'requirements' => ['nodeType' => '(\d+)', 'nodeId' => '(\d+)']],
Expand Down
1 change: 1 addition & 0 deletions lib/Constants/ColumnType.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ enum ColumnType: string {
case SELECTION = 'selection';
case DATETIME = 'datetime';
case PEOPLE = 'usergroup';
case RELATION = 'relation';
}
1 change: 1 addition & 0 deletions lib/Constants/ViewUpdatableParameters.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ enum ViewUpdatableParameters: string {
case TITLE = 'title';
case EMOJI = 'emoji';
case DESCRIPTION = 'description';
case TYPE = 'type';
case SORT = 'sort';
case FILTER = 'filter';
case COLUMN_SETTINGS = 'columns';
Expand Down
44 changes: 42 additions & 2 deletions lib/Controller/Api1Controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
use OCA\Tables\ResponseDefinitions;
use OCA\Tables\Service\ColumnService;
use OCA\Tables\Service\ImportService;
use OCA\Tables\Service\RelationService;
use OCA\Tables\Service\RowService;
use OCA\Tables\Service\ShareService;
use OCA\Tables\Service\TableService;
Expand Down Expand Up @@ -69,6 +70,8 @@ class Api1Controller extends ApiController {

protected LoggerInterface $logger;

private RelationService $relationService;

use Errors;


Expand All @@ -85,6 +88,7 @@ public function __construct(
LoggerInterface $logger,
IL10N $l10N,
?string $userId,
RelationService $relationService,
) {
parent::__construct(Application::APP_ID, $request);
$this->tableService = $service;
Expand All @@ -98,6 +102,7 @@ public function __construct(
$this->v1Api = $v1Api;
$this->logger = $logger;
$this->l10N = $l10N;
$this->relationService = $relationService;
}

// Tables
Expand Down Expand Up @@ -346,9 +351,9 @@ public function indexViews(int $tableId): DataResponse {
#[CORS]
#[RequirePermission(permission: Application::PERMISSION_MANAGE, type: Application::NODE_TYPE_TABLE, idParam: 'tableId')]
#[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
public function createView(int $tableId, string $title, ?string $emoji): DataResponse {
public function createView(int $tableId, string $title, ?string $emoji, ?string $type = 'table'): DataResponse {
try {
return new DataResponse($this->viewService->create($title, $emoji, $this->tableService->find($tableId))->jsonSerialize());
return new DataResponse($this->viewService->create($title, $emoji, $this->tableService->find($tableId), null, $type)->jsonSerialize());
} catch (PermissionError $e) {
$this->logger->warning('A permission error occurred: ' . $e->getMessage(), ['exception' => $e]);
$message = ['message' => $e->getMessage()];
Expand Down Expand Up @@ -1729,4 +1734,39 @@ public function createTableColumn(
return new DataResponse($message, Http::STATUS_BAD_REQUEST);
}
}

// Relations

/**
* @NoAdminRequired
*/
#[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
public function getRelations(int $columnId, int $rowId): DataResponse {
try {
$linkedIds = $this->relationService->getLinkedRowIds($rowId, $columnId);
return new DataResponse($linkedIds);
} catch (\Exception $e) {
return new DataResponse(['message' => $e->getMessage()], Http::STATUS_INTERNAL_SERVER_ERROR);
}
}

/**
* @NoAdminRequired
*/
#[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
public function setRelations(int $columnId, int $rowId, array $targetRowIds = []): DataResponse {
try {
$column = $this->columnService->find($columnId);
$userId = $this->userId;
$this->relationService->setLinks($columnId, $rowId, $targetRowIds, $userId, $column);
$linkedIds = $this->relationService->getLinkedRowIds($rowId, $columnId);
return new DataResponse($linkedIds);
} catch (\Exception $e) {
return new DataResponse(['message' => $e->getMessage()], Http::STATUS_INTERNAL_SERVER_ERROR);
}
}
}
58 changes: 58 additions & 0 deletions lib/Controller/ApiColumnsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -412,4 +412,62 @@ public function createUsergroupColumn(int $baseNodeId, string $title, ?string $u
);
return new DataResponse($column->jsonSerialize());
}

/**
* [api v2] Create new relation column
*
* Links rows from one table to rows in another table
*
* @param int $baseNodeId Context of the column creation
* @param string $title Title
* @param int $relationTableId ID of the related table
* @param boolean $relationMultiple Whether multiple rows can be linked
* @param string $relationType Relation type: 'one-to-one', 'one-to-many', 'many-to-many'
* @param int|null $relationDisplayColumnId Column ID from target table to display
* @param string|null $description Description
* @param list<int>|null $selectedViewIds View IDs where this columns
* should be added
* @param boolean $mandatory Is mandatory
* @param 'table'|'view' $baseNodeType Context type of the column creation
* @param array<string, mixed> $customSettings Custom settings for the
* column
*
*
* @return DataResponse<Http::STATUS_OK, TablesColumn,
* array{}>|DataResponse<Http::STATUS_FORBIDDEN|Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_NOT_FOUND,
* array{message: string}, array{}>
*
*
* 200: Column created
* 403: No permission
* 404: Not found
* @throws InternalError
* @throws NotFoundError
* @throws PermissionError
* @throws BadRequestError
*/
#[NoAdminRequired]
#[RequirePermission(permission: Application::PERMISSION_MANAGE, typeParam: 'baseNodeType', idParam: 'baseNodeId')]
public function createRelationColumn(int $baseNodeId, string $title, int $relationTableId, ?bool $relationMultiple = false, string $relationType = 'many-to-many', ?int $relationDisplayColumnId = null, ?string $description = null, ?array $selectedViewIds = [], bool $mandatory = false, string $baseNodeType = 'table', array $customSettings = []): DataResponse {
$tableId = $baseNodeType === 'table' ? $baseNodeId : null;
$viewId = $baseNodeType === 'view' ? $baseNodeId : null;
$column = $this->service->create(
$this->userId,
$tableId,
$viewId,
new ColumnDto(
title: $title,
type: ColumnType::RELATION->value,
mandatory: $mandatory,
description: $description,
customSettings: json_encode($customSettings),
relationTableId: $relationTableId,
relationMultiple: $relationMultiple,
relationType: $relationType,
relationDisplayColumnId: $relationDisplayColumnId,
),
$selectedViewIds
);
return new DataResponse($column->jsonSerialize());
}
}
6 changes: 3 additions & 3 deletions lib/Controller/ViewController.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,9 @@ public function show(int $id): DataResponse {

#[NoAdminRequired]
#[RequirePermission(permission: Application::PERMISSION_MANAGE, type: Application::NODE_TYPE_TABLE, idParam: 'tableId')]
public function create(int $tableId, string $title, ?string $emoji): DataResponse {
return $this->handleError(function () use ($tableId, $title, $emoji) {
return $this->service->create($title, $emoji, $this->getTable($tableId, true));
public function create(int $tableId, string $title, ?string $emoji, ?string $type = 'table'): DataResponse {
return $this->handleError(function () use ($tableId, $title, $emoji, $type) {
return $this->service->create($title, $emoji, $this->getTable($tableId, true), null, $type);
});
}

Expand Down
27 changes: 27 additions & 0 deletions lib/Db/Column.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ class Column extends EntitySuper implements JsonSerializable {
public const TYPE_NUMBER = 'number';
public const TYPE_DATETIME = 'datetime';
public const TYPE_USERGROUP = 'usergroup';
public const TYPE_RELATION = 'relation';

public const SUBTYPE_DATETIME_DATE = 'date';
public const SUBTYPE_DATETIME_TIME = 'time';
Expand Down Expand Up @@ -156,6 +157,13 @@ class Column extends EntitySuper implements JsonSerializable {
protected ?bool $showUserStatus = null;
protected ?string $customSettings = null;

// type relation
protected ?int $relationTableId = null;
protected ?bool $relationMultiple = null;
protected ?int $relationTargetColumnId = null;
protected ?string $relationType = null;
protected ?int $relationDisplayColumnId = null;

// virtual properties
protected ?string $createdByDisplayName = null;
protected ?string $lastEditByDisplayName = null;
Expand Down Expand Up @@ -186,6 +194,13 @@ public function __construct() {
$this->addType('showUserStatus', 'boolean');

$this->addType('customSettings', 'string');

// type relation
$this->addType('relationTableId', 'integer');
$this->addType('relationMultiple', 'boolean');
$this->addType('relationTargetColumnId', 'integer');
$this->addType('relationType', 'string');
$this->addType('relationDisplayColumnId', 'integer');
}

public static function isValidMetaTypeId(int $metaTypeId): bool {
Expand Down Expand Up @@ -225,6 +240,11 @@ public static function fromDto(ColumnDto $data): self {
$column->setUsergroupSelectTeams($data->getUsergroupSelectTeams());
$column->setShowUserStatus($data->getShowUserStatus());
$column->setCustomSettings($data->getCustomSettings());
$column->setRelationTableId($data->getRelationTableId());
$column->setRelationMultiple($data->getRelationMultiple());
$column->setRelationTargetColumnId($data->getRelationTargetColumnId());
$column->setRelationType($data->getRelationType());
$column->setRelationDisplayColumnId($data->getRelationDisplayColumnId());
return $column;
}

Expand Down Expand Up @@ -305,6 +325,13 @@ public function jsonSerialize(): array {
'usergroupSelectTeams' => $this->usergroupSelectTeams,
'showUserStatus' => $this->showUserStatus,
'customSettings' => $this->getCustomSettingsArray() ?: new \stdClass(),

// type relation
'relationTableId' => $this->getRelationTableId(),
'relationMultiple' => $this->getRelationMultiple(),
'relationTargetColumnId' => $this->getRelationTargetColumnId(),
'relationType' => $this->getRelationType(),
'relationDisplayColumnId' => $this->getRelationDisplayColumnId(),
];
}

Expand Down
12 changes: 12 additions & 0 deletions lib/Db/ColumnTypes/RelationColumnQB.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace OCA\Tables\Db\ColumnTypes;

use OCP\DB\IResult;
use OCP\DB\QueryBuilder\IQueryBuilder;

class RelationColumnQB extends SuperColumnQB implements IColumnTypeQB {
// Use default implementations from SuperColumnQB
}
18 changes: 18 additions & 0 deletions lib/Db/RowCellRelation.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace OCA\Tables\Db;

class RowCellRelation extends RowCellSuper {

Check failure on line 7 in lib/Db/RowCellRelation.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis dev-master

MissingTemplateParam

lib/Db/RowCellRelation.php:7:7: MissingTemplateParam: OCA\Tables\Db\RowCellRelation has missing template params when extending OCA\Tables\Db\RowCellSuper, expecting 1 (see https://psalm.dev/182)

Check failure on line 7 in lib/Db/RowCellRelation.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis dev-stable33

MissingTemplateParam

lib/Db/RowCellRelation.php:7:7: MissingTemplateParam: OCA\Tables\Db\RowCellRelation has missing template params when extending OCA\Tables\Db\RowCellSuper, expecting 1 (see https://psalm.dev/182)
protected ?string $value = null;

public function __construct() {
parent::__construct();
$this->addType('value', 'string');
}

public function jsonSerialize(): array {
return parent::jsonSerializePreparation($this->value);
}
}
36 changes: 36 additions & 0 deletions lib/Db/RowCellRelationMapper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace OCA\Tables\Db;

use OCP\IDBConnection;

/**
* @template-extends RowCellMapperSuper<RowCellRelation, string, string|array>
*/
class RowCellRelationMapper extends RowCellMapperSuper {
use RowCellBulkFetchTrait;

protected string $table = 'tables_row_cells_relation';

public function __construct(IDBConnection $db) {
parent::__construct($db, $this->table, RowCellRelation::class);
}

public function filterValueToQueryParam(Column $column, mixed $value): mixed {
return $value ?? '';
}

public function applyDataToEntity(Column $column, RowCellSuper $cell, $data): void {
if (is_array($data)) {
$cell->setValue(json_encode($data));
} else {
$cell->setValue($data);
}
}

public function formatEntity(Column $column, RowCellSuper $cell) {
return json_decode($cell->getValue());
}
}
46 changes: 46 additions & 0 deletions lib/Db/RowRelation.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

declare(strict_types=1);

namespace OCA\Tables\Db;

use OCP\AppFramework\Db\Entity;

/**
* @method int getRelationColumnId()
* @method void setRelationColumnId(int $relationColumnId)
* @method int getSourceRowId()
* @method void setSourceRowId(int $sourceRowId)
* @method int getTargetRowId()
* @method void setTargetRowId(int $targetRowId)
* @method string getCreatedBy()
* @method void setCreatedBy(string $createdBy)
* @method string getCreatedAt()
* @method void setCreatedAt(string $createdAt)
*/
class RowRelation extends Entity implements \JsonSerializable {
protected ?int $relationColumnId = null;
protected ?int $sourceRowId = null;
protected ?int $targetRowId = null;
protected ?string $createdBy = null;
protected ?string $createdAt = null;

public function __construct() {
$this->addType('relationColumnId', 'integer');
$this->addType('sourceRowId', 'integer');
$this->addType('targetRowId', 'integer');
$this->addType('createdBy', 'string');
$this->addType('createdAt', 'string');
}

public function jsonSerialize(): array {
return [
'id' => $this->getId(),
'relationColumnId' => $this->getRelationColumnId(),
'sourceRowId' => $this->getSourceRowId(),
'targetRowId' => $this->getTargetRowId(),
'createdBy' => $this->getCreatedBy(),
'createdAt' => $this->getCreatedAt(),
];
}
}
Loading
Loading