Skip to content

Commit ac3b696

Browse files
committed
feat: Add OpenAPI
Signed-off-by: provokateurin <[email protected]>
1 parent ffcd9c0 commit ac3b696

37 files changed

+4071
-342
lines changed

β€Ž.eslintrc.jsβ€Ž

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,15 @@ module.exports = {
66
extends: [
77
'@nextcloud/eslint-config/typescript',
88
],
9+
overrides: [
10+
{
11+
files: ['src/types/openapi/*.ts'],
12+
rules: {
13+
'@typescript-eslint/no-explicit-any': 'off',
14+
quotes: 'off',
15+
'no-multiple-empty-lines': 'off',
16+
'no-use-before-define': 'off',
17+
},
18+
},
19+
],
920
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# This workflow is provided via the organization template repository
2+
#
3+
# https://github.com/nextcloud/.github
4+
# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
5+
#
6+
# SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
7+
# SPDX-FileCopyrightText: 2024 Arthur Schiwon <[email protected]>
8+
# SPDX-License-Identifier: MIT
9+
10+
name: OpenAPI
11+
12+
on: pull_request
13+
14+
permissions:
15+
contents: read
16+
17+
concurrency:
18+
group: openapi-${{ github.head_ref || github.run_id }}
19+
cancel-in-progress: true
20+
21+
jobs:
22+
openapi:
23+
runs-on: ubuntu-latest
24+
25+
if: ${{ github.repository_owner != 'nextcloud-gmbh' }}
26+
27+
steps:
28+
- name: Checkout
29+
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
30+
31+
- name: Get php version
32+
id: php_versions
33+
uses: icewind1991/nextcloud-version-matrix@58becf3b4bb6dc6cef677b15e2fd8e7d48c0908f # v1.3.1
34+
35+
- name: Set up php
36+
uses: shivammathur/setup-php@c541c155eee45413f5b09a52248675b1a2575231 # v2.31.1
37+
with:
38+
php-version: ${{ steps.php_versions.outputs.php-available }}
39+
extensions: xml
40+
coverage: none
41+
ini-file: development
42+
env:
43+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
44+
45+
- name: Check Typescript OpenApi types
46+
id: check_typescript_openapi
47+
uses: andstor/file-existence-action@076e0072799f4942c8bc574a82233e1e4d13e9d6 # v3.0.0
48+
with:
49+
files: "src/types/openapi/openapi*.ts"
50+
51+
- name: Read package.json node and npm engines version
52+
if: steps.check_typescript_openapi.outputs.files_exists == 'true'
53+
uses: skjnldsv/read-package-engines-version-actions@06d6baf7d8f41934ab630e97d9e6c0bc9c9ac5e4 # v3
54+
id: node_versions
55+
# Continue if no package.json
56+
continue-on-error: true
57+
with:
58+
fallbackNode: '^20'
59+
fallbackNpm: '^10'
60+
61+
- name: Set up node ${{ steps.node_versions.outputs.nodeVersion }}
62+
if: ${{ steps.node_versions.outputs.nodeVersion }}
63+
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
64+
with:
65+
node-version: ${{ steps.node_versions.outputs.nodeVersion }}
66+
67+
- name: Set up npm ${{ steps.node_versions.outputs.npmVersion }}
68+
if: ${{ steps.node_versions.outputs.nodeVersion }}
69+
run: npm i -g 'npm@${{ steps.node_versions.outputs.npmVersion }}'
70+
71+
- name: Install dependencies & build
72+
if: ${{ steps.node_versions.outputs.nodeVersion }}
73+
env:
74+
CYPRESS_INSTALL_BINARY: 0
75+
PUPPETEER_SKIP_DOWNLOAD: true
76+
run: |
77+
npm ci
78+
79+
- name: Set up dependencies
80+
run: composer i
81+
82+
- name: Regenerate OpenAPI
83+
run: composer run openapi
84+
85+
- name: Check openapi*.json and typescript changes
86+
run: |
87+
bash -c "[[ ! \"`git status --porcelain `\" ]] || (echo 'Please run \"composer run openapi\" and commit the openapi*.json files and (if applicable) src/types/openapi/openapi*.ts, see the section \"Show changes on failure\" for details' && exit 1)"
88+
89+
- name: Show changes on failure
90+
if: failure()
91+
run: |
92+
git status
93+
git --no-pager diff
94+
exit 1 # make it red to grab attention

β€ŽREADME.mdβ€Ž

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -126,35 +126,7 @@ To disable the advanced permissions feature for a group folder, use `occ groupfo
126126

127127
### REST API
128128

129-
Group folders can be configured externally through REST APIs.
130-
131-
The following REST API's are supported:
132-
133-
- `GET apps/groupfolders/folders`: Returns a list of all configured folders and their settings
134-
- `POST apps/groupfolders/folders`: Create a new group folder
135-
- `mountpoint`: The name for the new folder
136-
- `GET apps/groupfolders/folders/$folderId`: Return a specific configured folder and its settings
137-
- `DELETE apps/groupfolders/folders/$folderId`: Delete a group folder
138-
- `POST apps/groupfolders/folders/$folderId/groups`: Give a group access to a folder
139-
- `group`: The id of the group to be given access to the folder
140-
- `DELETE apps/groupfolders/folders/$folderId/groups/$groupId`: Remove access from a group to a folder
141-
- `POST apps/groupfolders/folders/$folderId/acl`: Enable/Disable folder advanced permissions
142-
- `acl` 1 for enable, 0 for disable.
143-
- `POST apps/groupfolders/folders/$folderId/manageACL`: Grants/Removes a group or user the ability to manage a groupfolders' advanced permissions
144-
- `$mappingId`: the id of the group/user to be granted/removed access to/from the folder
145-
- `$mappingType`: 'group' or 'user'
146-
- `$manageAcl`: true to grants ability to manage a groupfolders' advanced permissions, false to remove
147-
- `POST apps/groupfolders/folders/$folderId/groups/$groupId`: Set the permissions a group has in a folder
148-
- `permissions` The new permissions for the group as bitmask of [permissions constants](https://github.com/nextcloud/server/blob/b4f36d44c43aac0efdc6c70ff8e46473341a9bfe/lib/public/Constants.php#L65)
149-
- `POST apps/groupfolders/folders/$folderId/quota`: Set the quota for a folder
150-
- `quota`: The new quota for the folder in bytes, user `-3` for unlimited
151-
- `POST apps/groupfolders/folders/$folderId/mountpoint`: Change the name of a folder
152-
- `mountpoint`: The new name for the folder
153-
154-
For all `POST` calls the required parameters are listed.
155-
156-
Non-admins can access the `GET` requests to retrieve info about group folders they have access to.
157-
Admins can add `applicable=1` as a parameter to the group folder list request to get the same filtered results of only folders they have direct access to.
129+
See the [OpenAPI specification](openapi.json) to learn about all available API endpoints.
158130

159131
### WebDAV API
160132

β€ŽREUSE.tomlβ€Ž

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,3 +76,9 @@ path = ["tests/stubs/icewind_streams_**"]
7676
precedence = "aggregate"
7777
SPDX-FileCopyrightText = "none"
7878
SPDX-License-Identifier = "MIT"
79+
80+
[[annotations]]
81+
path = ["openapi.json", "src/types/openapi/openapi.ts"]
82+
precedence = "aggregate"
83+
SPDX-FileCopyrightText = "2024 Nextcloud GmbH and Nextcloud contributors"
84+
SPDX-License-Identifier = "AGPL-3.0-or-later"

β€Žcomposer.jsonβ€Ž

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323
"psalm:fix": "psalm --no-cache --alter --issues=InvalidReturnType,InvalidNullableReturnType,MissingParamType,InvalidFalsableReturnType",
2424
"test:unit": "phpunit -c tests/phpunit.xml",
2525
"test:unit:coverage": "XDEBUG_MODE=coverage phpunit -c tests/phpunit.xml",
26-
"rector": "rector && composer cs:fix"
26+
"rector": "rector && composer cs:fix",
27+
"openapi": "generate-spec && npm run typescript:generate"
2728
},
2829
"config": {
2930
"allow-plugins": {

β€Žlib/AppInfo/Capabilities.phpβ€Ž

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,14 @@ public function __construct(
2121
) {
2222
}
2323

24+
/**
25+
* @return array{
26+
* groupfolders?: array{
27+
* appVersion: string,
28+
* hasGroupFolders: bool,
29+
* },
30+
* }
31+
*/
2432
public function getCapabilities(): array {
2533
$user = $this->userSession->getUser();
2634
if (!$user) {

β€Žlib/Command/FolderCommand.phpβ€Ž

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,15 @@
1111
use OC\Core\Command\Base;
1212
use OCA\GroupFolders\Folder\FolderManager;
1313
use OCA\GroupFolders\Mount\MountProvider;
14+
use OCA\GroupFolders\ResponseDefinitions;
1415
use OCP\Files\IRootFolder;
1516
use Symfony\Component\Console\Input\InputInterface;
1617
use Symfony\Component\Console\Output\OutputInterface;
1718

1819
/**
1920
* Base command for commands asking the user for a folder id.
21+
*
22+
* @psalm-import-type GroupFoldersFolder from ResponseDefinitions
2023
*/
2124
abstract class FolderCommand extends Base {
2225

@@ -29,7 +32,7 @@ public function __construct(
2932
}
3033

3134
/**
32-
* @psalm-return ?array{id: mixed, mount_point: string, groups: array<empty, empty>|mixed, quota: int, size: int|mixed, acl: bool}
35+
* @return ?GroupFoldersFolder
3336
*/
3437
protected function getFolder(InputInterface $input, OutputInterface $output): ?array {
3538
$folderId = (int)$input->getArgument('folder_id');

β€Žlib/Command/Group.phpβ€Ž

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int
6262
$permissionsString = $input->getArgument('permissions');
6363
$permissions = $this->getNewPermissions($permissionsString);
6464
if ($permissions) {
65-
if (!isset($folder['groups'][$groupString])) {
65+
if (!is_array($folder['groups'])) {
66+
$folder['groups'] = [];
67+
}
68+
if (empty(array_filter($folder['groups'], static fn (array $group) => $group['id'] === $groupString))) {
6669
$this->folderManager->addApplicableGroup($folder['id'], $groupString);
6770
}
6871

β€Žlib/Command/ListCommand.phpβ€Ž

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int
8787

8888
if ($outputType === self::OUTPUT_FORMAT_JSON || $outputType === self::OUTPUT_FORMAT_JSON_PRETTY) {
8989
foreach ($folders as &$folder) {
90-
$folder['group_details'] = $folder['groups'];
91-
$folder['groups'] = array_map(fn (array $group): int => $group['permissions'], $folder['groups']);
90+
if (is_array($folder['groups'])) {
91+
$folder['groups'] = array_map(fn (array $group): int => $group['permissions'], $folder['groups']);
92+
} else {
93+
$folder['groups'] = [];
94+
}
9295
}
9396

9497
$this->writeArrayInOutputFormat($input, $output, $folders);
@@ -98,13 +101,17 @@ protected function execute(InputInterface $input, OutputInterface $output): int
98101
$table->setRows(array_map(function (array $folder) use ($groupNames): array {
99102
$folder['size'] = \OCP\Util::humanFileSize($folder['size']);
100103
$folder['quota'] = ($folder['quota'] > 0) ? \OCP\Util::humanFileSize($folder['quota']) : 'Unlimited';
101-
$groupStrings = array_map(function (string $groupId, array $entry) use ($groupNames): string {
102-
[$permissions, $displayName] = [$entry['permissions'], $entry['displayName']];
103-
$groupName = array_key_exists($groupId, $groupNames) && ($groupNames[$groupId] !== $groupId) ? $groupNames[$groupId] . ' (' . $groupId . ')' : $displayName;
104-
105-
return $groupName . ': ' . $this->permissionsToString($permissions);
106-
}, array_keys($folder['groups']), array_values($folder['groups']));
107-
$folder['groups'] = implode("\n", $groupStrings);
104+
if (is_array($folder['groups'])) {
105+
$groupStrings = array_map(function (string $groupId, array $entry) use ($groupNames): string {
106+
[$permissions, $displayName] = [$entry['permissions'], $entry['displayName']];
107+
$groupName = array_key_exists($groupId, $groupNames) && ($groupNames[$groupId] !== $groupId) ? $groupNames[$groupId] . ' (' . $groupId . ')' : $displayName;
108+
109+
return $groupName . ': ' . $this->permissionsToString($permissions);
110+
}, array_keys($folder['groups']), array_values($folder['groups']));
111+
$folder['groups'] = implode("\n", $groupStrings);
112+
} else {
113+
$folder['groups'] = [];
114+
}
108115
$folder['acl'] = $folder['acl'] ? 'Enabled' : 'Disabled';
109116
$manageStrings = array_map(fn (array $manage): string => $manage['id'] . ' (' . $manage['type'] . ')', $folder['manage']);
110117
$folder['manage'] = implode("\n", $manageStrings);

β€Žlib/Command/Scan.phpβ€Ž

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
6767
return -1;
6868
}
6969

70-
$folders = [$folder['id'] => $folder];
70+
$folders = [$folder];
7171
}
7272

7373
$inputPath = $input->getOption('path');

0 commit comments

Comments
Β (0)