Skip to content

Commit 35b4e5e

Browse files
committed
feat(federation): view federation
Signed-off-by: Benjamin Frueh <benjamin.frueh@gmail.com>
1 parent b3b9bac commit 35b4e5e

19 files changed

Lines changed: 333 additions & 152 deletions

appinfo/routes.php

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -155,9 +155,5 @@
155155
['name' => 'Context#destroy', 'url' => '/api/2/contexts/{contextId}', 'verb' => 'DELETE'],
156156
['name' => 'Context#transfer', 'url' => '/api/2/contexts/{contextId}/transfer', 'verb' => 'PUT'],
157157
['name' => 'Context#updateContentOrder', 'url' => '/api/2/contexts/{contextId}/pages/{pageId}', 'verb' => 'PUT'],
158-
159-
['name' => 'RowOCS#createRow', 'url' => '/api/2/{nodeCollection}/{nodeId}/rows', 'verb' => 'POST', 'requirements' => ['nodeCollection' => '(tables|views)', 'nodeId' => '(\d+)']],
160-
['name' => 'RowOCS#updateRow', 'url' => '/api/2/{nodeCollection}/{nodeId}/rows/{rowId}', 'verb' => 'PUT', 'requirements' => ['nodeCollection' => '(tables|views)', 'nodeId' => '(\d+)']],
161-
['name' => 'RowOCS#deleteRow', 'url' => '/api/2/{nodeCollection}/{nodeId}/rows/{rowId}', 'verb' => 'DELETE', 'requirements' => ['nodeCollection' => '(tables|views)', 'nodeId' => '(\d+)']],
162158
]
163159
];

lib/Controller/Api1Controller.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -772,8 +772,8 @@ public function updateShareDisplayMode(int $shareId, int $displayMode, string $t
772772
#[CORS]
773773
#[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
774774
public function indexTableColumns(int $tableId, ?int $viewId): DataResponse {
775-
$table = $this->tableService->find($tableId, true);
776-
if ($table->isFederated()) {
775+
if ($this->tableService->isFederated($tableId)) {
776+
$table = $this->tableService->find($tableId, true);
777777
return new DataResponse($this->federationService->getColumns($table));
778778
}
779779

@@ -820,6 +820,11 @@ public function indexTableColumns(int $tableId, ?int $viewId): DataResponse {
820820
#[RequirePermission(permission: Application::PERMISSION_READ, type: Application::NODE_TYPE_VIEW, idParam: 'viewId')]
821821
#[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
822822
public function indexViewColumns(int $viewId): DataResponse {
823+
if ($this->viewService->isFederated($viewId)) {
824+
$view = $this->viewService->find($viewId, true);
825+
return new DataResponse($this->federationService->getColumns($view));
826+
}
827+
823828
try {
824829
return new DataResponse($this->columnService->formatColumns($this->columnService->findAllByView($viewId)));
825830
} catch (PermissionError $e) {

lib/Controller/RowController.php

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use OCA\Tables\Service\FederationService;
1313
use OCA\Tables\Service\RowService;
1414
use OCA\Tables\Service\TableService;
15+
use OCA\Tables\Service\ViewService;
1516
use OCP\AppFramework\Controller;
1617
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
1718
use OCP\AppFramework\Http\DataResponse;
@@ -27,6 +28,7 @@ public function __construct(
2728
private RowService $service,
2829
private ?string $userId,
2930
private TableService $tableService,
31+
private ViewService $viewService,
3032
private FederationService $federationService,
3133
) {
3234
parent::__construct(Application::APP_ID, $request);
@@ -35,8 +37,8 @@ public function __construct(
3537
#[NoAdminRequired]
3638
#[RequirePermission(permission: Application::PERMISSION_READ, type: Application::NODE_TYPE_TABLE, idParam: 'tableId')]
3739
public function index(int $tableId): DataResponse {
38-
$table = $this->tableService->find($tableId, true);
39-
if ($table->isFederated()) {
40+
if ($this->tableService->isFederated($tableId)) {
41+
$table = $this->tableService->find($tableId, true);
4042
return new DataResponse($this->federationService->getRows($table));
4143
}
4244
return $this->handleError(function () use ($tableId) {
@@ -47,6 +49,10 @@ public function index(int $tableId): DataResponse {
4749
#[NoAdminRequired]
4850
#[RequirePermission(permission: Application::PERMISSION_READ, type: Application::NODE_TYPE_VIEW, idParam: 'viewId')]
4951
public function indexView(int $viewId): DataResponse {
52+
if ($this->viewService->isFederated($viewId)) {
53+
$view = $this->viewService->find($viewId, false, $this->userId);
54+
return new DataResponse($this->federationService->getRows($view));
55+
}
5056
return $this->handleError(function () use ($viewId) {
5157
return $this->service->findAllByView($viewId, $this->userId);
5258
});
@@ -106,6 +112,9 @@ public function destroyByView(int $id, int $viewId): DataResponse {
106112

107113
#[NoAdminRequired]
108114
public function presentInView(int $id, int $viewId): DataResponse {
115+
if ($this->viewService->isFederated($viewId)) {
116+
return new DataResponse(['present' => true]);
117+
}
109118
return $this->handleError(function () use ($id, $viewId) {
110119
$present = $this->service->isRowInViewPresent($id, $viewId, $this->userId);
111120
return ['present' => $present];

lib/Controller/RowOCSController.php

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
use OCA\Tables\Service\FederationService;
2020
use OCA\Tables\Service\RowService;
2121
use OCA\Tables\Service\TableService;
22+
use OCA\Tables\Service\ViewService;
2223
use OCP\AppFramework\Http;
24+
use OCP\AppFramework\Http\Attribute\ApiRoute;
2325
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
2426
use OCP\AppFramework\Http\DataResponse;
2527
use OCP\IL10N;
@@ -38,6 +40,7 @@ public function __construct(
3840
string $userId,
3941
protected RowService $rowService,
4042
private TableService $tableService,
43+
private ViewService $viewService,
4144
private FederationService $federationService,
4245
) {
4346
parent::__construct($request, $logger, $n, $userId);
@@ -59,6 +62,7 @@ public function __construct(
5962
*/
6063
#[NoAdminRequired]
6164
#[RequirePermission(permission: Application::PERMISSION_CREATE, typeParam: 'nodeCollection')]
65+
#[ApiRoute(verb: 'POST', url: '/api/2/{nodeCollection}/{nodeId}/rows', requirements: ['nodeCollection' => '(tables|views)', 'nodeId' => '(\d+)'])]
6266
public function createRow(string $nodeCollection, int $nodeId, mixed $data): DataResponse {
6367
if (is_string($data)) {
6468
$data = json_decode($data, true);
@@ -71,12 +75,16 @@ public function createRow(string $nodeCollection, int $nodeId, mixed $data): Dat
7175
$tableId = $viewId = null;
7276
if ($iNodeType === Application::NODE_TYPE_TABLE) {
7377
$tableId = $nodeId;
74-
$table = $this->tableService->find($nodeId, true);
75-
if ($table->isFederated()) {
78+
if ($this->tableService->isFederated($tableId)) {
79+
$table = $this->tableService->find($nodeId, true);
7680
return new DataResponse($this->federationService->createRow($table, $data));
7781
}
7882
} elseif ($iNodeType === Application::NODE_TYPE_VIEW) {
7983
$viewId = $nodeId;
84+
if ($this->viewService->isFederated($viewId)) {
85+
$view = $this->viewService->find($nodeId, false, $this->userId);
86+
return new DataResponse($this->federationService->createRow($view, $data));
87+
}
8088
}
8189

8290
$newRowData = new RowDataInput();
@@ -113,6 +121,7 @@ public function createRow(string $nodeCollection, int $nodeId, mixed $data): Dat
113121
*/
114122
#[NoAdminRequired]
115123
#[RequirePermission(permission: Application::PERMISSION_UPDATE, typeParam: 'nodeCollection')]
124+
#[ApiRoute(verb: 'PUT', url: '/api/2/{nodeCollection}/{nodeId}/rows/{rowId}', requirements: ['nodeCollection' => '(tables|views)', 'nodeId' => '(\d+)'])]
116125
public function updateRow(string $nodeCollection, int $nodeId, int $rowId, mixed $data): DataResponse {
117126
if (is_string($data)) {
118127
$data = json_decode($data, true);
@@ -121,12 +130,19 @@ public function updateRow(string $nodeCollection, int $nodeId, int $rowId, mixed
121130
return $this->handleBadRequestError(new BadRequestError('Cannot update row: data input is invalid.'));
122131
}
123132
$iNodeType = ConversionHelper::stringNodeType2Const($nodeCollection);
124-
$viewId = $iNodeType === Application::NODE_TYPE_VIEW ? $nodeId : null;
133+
$tableId = $viewId = null;
125134
if ($iNodeType === Application::NODE_TYPE_TABLE) {
126-
$table = $this->tableService->find($nodeId, true);
127-
if ($table->isFederated()) {
135+
$tableId = $nodeId;
136+
if ($this->tableService->isFederated($tableId)) {
137+
$table = $this->tableService->find($nodeId, true);
128138
return new DataResponse($this->federationService->updateRow($table, $rowId, $data));
129139
}
140+
} elseif ($iNodeType === Application::NODE_TYPE_VIEW) {
141+
$viewId = $nodeId;
142+
if ($this->viewService->isFederated($viewId)) {
143+
$view = $this->viewService->find($nodeId, false, $this->userId);
144+
return new DataResponse($this->federationService->updateRow($view, $rowId, $data));
145+
}
130146
}
131147
try {
132148
return new DataResponse($this->rowService->updateSet($rowId, $viewId, $data, $this->userId, null)->jsonSerialize());
@@ -154,17 +170,25 @@ public function updateRow(string $nodeCollection, int $nodeId, int $rowId, mixed
154170
*/
155171
#[NoAdminRequired]
156172
#[RequirePermission(permission: Application::PERMISSION_DELETE, typeParam: 'nodeCollection')]
173+
#[ApiRoute(verb: 'DELETE', url: '/api/2/{nodeCollection}/{nodeId}/rows/{rowId}', requirements: ['nodeCollection' => '(tables|views)', 'nodeId' => '(\d+)'])]
157174
public function deleteRow(string $nodeCollection, int $nodeId, int $rowId): DataResponse {
158175
$iNodeType = ConversionHelper::stringNodeType2Const($nodeCollection);
176+
$tableId = $viewId = null;
159177
if ($iNodeType === Application::NODE_TYPE_TABLE) {
160-
$table = $this->tableService->find($nodeId, true);
161-
if ($table->isFederated()) {
178+
$tableId = $nodeId;
179+
if ($this->tableService->isFederated($tableId)) {
180+
$table = $this->tableService->find($nodeId, true);
162181
return new DataResponse($this->federationService->deleteRow($table, $rowId));
163182
}
183+
} elseif ($iNodeType === Application::NODE_TYPE_VIEW) {
184+
$viewId = $nodeId;
185+
if ($this->viewService->isFederated($viewId)) {
186+
$view = $this->viewService->find($nodeId, false, $this->userId);
187+
return new DataResponse($this->federationService->deleteRow($view, $rowId));
188+
}
164189
}
165190
try {
166-
$viewId = $iNodeType === Application::NODE_TYPE_VIEW ? $nodeId : null;
167-
return new DataResponse($this->rowService->delete($rowId, $viewId, $this->userId)->jsonSerialize());
191+
return new DataResponse($this->rowService->delete($rowId, $viewId, $this->userId, $tableId)->jsonSerialize());
168192
} catch (NotFoundError $e) {
169193
return $this->handleNotFoundError($e);
170194
} catch (PermissionError $e) {

lib/Db/Table.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,6 @@ private function getArray(?string $json): array {
192192
}
193193

194194
public function isFederated(): bool {
195-
return $this->externalId !== null;
195+
return $this->externalId !== null && $this->shareToken !== null;
196196
}
197197
}

lib/Db/TableMapper.php

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,24 @@ public function findByExternalIdAndToken(int $externalId, string $shareToken): ?
114114
->from($this->table)
115115
->where($qb->expr()->eq('external_id', $qb->createNamedParameter($externalId, IQueryBuilder::PARAM_INT)))
116116
->andWhere($qb->expr()->eq('share_token', $qb->createNamedParameter($shareToken, IQueryBuilder::PARAM_STR)));
117-
$items = $this->findEntities($qb);
118-
return $items[0] ?? null;
117+
try {
118+
return $this->findEntity($qb);
119+
} catch (DoesNotExistException) {
120+
return null;
121+
}
122+
}
123+
124+
public function isFederated(int $id): bool {
125+
$qb = $this->db->getQueryBuilder();
126+
$qb->select('id')
127+
->from($this->table)
128+
->where($qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)))
129+
->andWhere($qb->expr()->isNotNull('external_id'))
130+
->andWhere($qb->expr()->isNotNull('share_token'));
131+
$result = $qb->executeQuery();
132+
$exists = $result->fetchOne() !== false;
133+
$result->closeCursor();
134+
return $exists;
119135
}
120136

121137
/**

lib/Db/View.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@
6161
* @method setOwnerDisplayName(string $ownerDisplayName)
6262
* @method getOwnership(): ?string
6363
* @method setOwnership(string $ownership)
64+
* @method getExternalId(): ?int
65+
* @method setExternalId(?int $externalId)
66+
* @method getShareToken(): ?string
67+
* @method setShareToken(?string $shareToken)
6468
*/
6569
class View extends EntitySuper implements JsonSerializable {
6670
protected ?string $title = null;
@@ -75,6 +79,9 @@ class View extends EntitySuper implements JsonSerializable {
7579
protected ?string $sort = null; // json
7680
protected ?string $filter = null; // json
7781

82+
protected ?int $externalId = null;
83+
protected ?string $shareToken = null;
84+
7885
// virtual properties
7986
protected ?bool $isShared = null;
8087
protected ?Permissions $onSharePermissions = null;
@@ -199,6 +206,7 @@ public function jsonSerialize(): array {
199206
'hasShares' => (bool)$this->hasShares,
200207
'rowsCount' => $this->rowsCount ?: 0,
201208
'ownerDisplayName' => $this->ownerDisplayName,
209+
'isFederated' => $this->isFederated(),
202210
];
203211
$serialisedJson['filter'] = $this->getFilterArray();
204212

@@ -214,4 +222,8 @@ public function getColumnIds(): array {
214222

215223
return array_map(static fn (ViewColumnInformation $column): int => $column->getId(), $columns);
216224
}
225+
226+
public function isFederated(): bool {
227+
return $this->externalId !== null && $this->shareToken !== null;
228+
}
217229
}

lib/Db/ViewMapper.php

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public function find(int $id): View {
4242
$qb = $this->db->getQueryBuilder();
4343
$qb->select('v.*', 't.ownership')
4444
->from($this->table, 'v')
45-
->innerJoin('v', 'tables_tables', 't', 't.id = v.table_id')
45+
->leftJoin('v', 'tables_tables', 't', 't.id = v.table_id')
4646
->where($qb->expr()->eq('v.id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
4747
$entity = $this->findEntity($qb);
4848
$this->cache[$cacheKey] = $entity;
@@ -75,7 +75,7 @@ public function findMany(array $ids): array {
7575
$qb = $this->db->getQueryBuilder();
7676
$qb->select('v.*', 't.ownership')
7777
->from($this->table, 'v')
78-
->innerJoin('v', 'tables_tables', 't', 't.id = v.table_id')
78+
->leftJoin('v', 'tables_tables', 't', 't.id = v.table_id')
7979
->where($qb->expr()->in('v.id', $qb->createNamedParameter($missingChunk, IQueryBuilder::PARAM_INT_ARRAY)));
8080

8181
foreach ($this->findEntities($qb) as $entity) {
@@ -87,6 +87,32 @@ public function findMany(array $ids): array {
8787
return $result;
8888
}
8989

90+
public function findByExternalIdAndToken(int $externalId, string $shareToken): ?View {
91+
$qb = $this->db->getQueryBuilder();
92+
$qb->select('*')
93+
->from($this->table)
94+
->where($qb->expr()->eq('external_id', $qb->createNamedParameter($externalId, IQueryBuilder::PARAM_INT)))
95+
->andWhere($qb->expr()->eq('share_token', $qb->createNamedParameter($shareToken, IQueryBuilder::PARAM_STR)));
96+
try {
97+
return $this->findEntity($qb);
98+
} catch (DoesNotExistException) {
99+
return null;
100+
}
101+
}
102+
103+
public function isFederated(int $id): bool {
104+
$qb = $this->db->getQueryBuilder();
105+
$qb->select('id')
106+
->from($this->table)
107+
->where($qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)))
108+
->andWhere($qb->expr()->isNotNull('external_id'))
109+
->andWhere($qb->expr()->isNotNull('share_token'));
110+
$result = $qb->executeQuery();
111+
$exists = $result->fetchOne() !== false;
112+
$result->closeCursor();
113+
return $exists;
114+
}
115+
90116
public function delete(Entity $entity): View {
91117
unset($this->cache[(string)$entity->getId()]);
92118
return parent::delete($entity);

0 commit comments

Comments
 (0)