Skip to content

Commit 4fe28e3

Browse files
authored
Merge pull request #1011 from nextcloud/docs/noid/openapi
Add OpenAPI docs
2 parents 862e87c + 9bc7d49 commit 4fe28e3

File tree

16 files changed

+1760
-31
lines changed

16 files changed

+1760
-31
lines changed

.github/workflows/openapi.yml

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 openapi* `\" ]] || (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 openapi*
94+
exit 1 # make it red to grab attention

REUSE.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,9 @@ path = ["vendor/geoip2/**", "vendor/GeoLite2-Country.mmdb"]
5858
precedence = "aggregate"
5959
SPDX-FileCopyrightText = "geoip2 contributors"
6060
SPDX-License-Identifier = "Apache-2.0"
61+
62+
[[annotations]]
63+
path = ["openapi.json", "openapi-**.json"]
64+
precedence = "aggregate"
65+
SPDX-FileCopyrightText = "2024 Nextcloud GmbH and Nextcloud contributors"
66+
SPDX-License-Identifier = "AGPL-3.0-or-later"

appinfo/routes.php

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,22 @@
55
*/
66

77
return [
8-
'ocs-resources' => [
9-
'terms' => [
10-
'url' => '/terms'
11-
],
12-
],
138
'ocs' => [
9+
[
10+
'name' => 'Terms#index',
11+
'url' => '/terms',
12+
'verb' => 'GET',
13+
],
14+
[
15+
'name' => 'Terms#create',
16+
'url' => '/terms',
17+
'verb' => 'POST',
18+
],
19+
[
20+
'name' => 'Terms#destroy',
21+
'url' => '/terms/{id}',
22+
'verb' => 'DELETE',
23+
],
1424
[
1525
'name' => 'Terms#getAdminFormData',
1626
'url' => '/terms/admin',

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"scripts": {
2828
"bin": "echo 'bin not installed'",
2929
"lint": "find . -name \\*.php -not -path './vendor/*' -print0 | xargs -0 -n1 php -l",
30+
"openapi": "generate-spec --verbose",
3031
"post-install-cmd": [
3132
"@composer bin all install --ansi",
3233
"composer dump-autoload"

lib/Controller/SigningController.php

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
1010
use OCA\TermsOfService\BackgroundJobs\CreateNotifications;
1111
use OCA\TermsOfService\Db\Entities\Signatory;
1212
use OCA\TermsOfService\Db\Mapper\SignatoryMapper;
13+
use OCP\AppFramework\Http;
14+
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
15+
use OCP\AppFramework\Http\Attribute\PublicPage;
16+
use OCP\AppFramework\Http\Attribute\UseSession;
1317
use OCP\AppFramework\Http\DataResponse;
1418
use OCP\AppFramework\OCSController;
1519
use OCP\BackgroundJob\IJobList;
@@ -67,12 +71,14 @@ protected function resetAllSignaturesEvent(): SignaturesResetEvent {
6771
}
6872

6973
/**
70-
* @NoAdminRequired
74+
* As a logged in user sign the terms
7175
*
72-
* @param int $termId
76+
* @param int $termId The terms the user signed
77+
* @return DataResponse<Http::STATUS_OK, array<empty>, array{}>
7378
*
74-
* @return DataResponse
79+
* 200: Signed successfully
7580
*/
81+
#[NoAdminRequired]
7682
public function signTerms(int $termId): DataResponse {
7783
$signatory = new Signatory();
7884
$signatory->setUserId($this->userId);
@@ -95,12 +101,15 @@ public function signTerms(int $termId): DataResponse {
95101

96102

97103
/**
98-
* @PublicPage
104+
* As a guest sign the terms
99105
*
100-
* @param int $termId
101-
* @UseSession
102-
* @return DataResponse
106+
* @param int $termId The terms the user signed
107+
* @return DataResponse<Http::STATUS_OK, array<empty>, array{}>
108+
*
109+
* 200: Signed successfully
103110
*/
111+
#[PublicPage]
112+
#[UseSession]
104113
public function signTermsPublic(int $termId): DataResponse {
105114
$uuid = $this->config->getAppValue(Application::APPNAME, 'term_uuid', '');
106115
$this->session->set('term_uuid', $uuid);
@@ -110,7 +119,11 @@ public function signTermsPublic(int $termId): DataResponse {
110119

111120

112121
/**
113-
* @return DataResponse
122+
* Reset the signatories of all accounts
123+
*
124+
* @return DataResponse<Http::STATUS_OK, array<empty>, array{}>
125+
*
126+
* 200: Reset successfully
114127
*/
115128
public function resetAllSignatories(): DataResponse {
116129
$this->signatoryMapper->deleteAllSignatories();

lib/Controller/TermsController.php

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
use OCA\TermsOfService\Db\Mapper\SignatoryMapper;
1717
use OCA\TermsOfService\Db\Mapper\TermsMapper;
1818
use OCA\TermsOfService\Exceptions\TermsNotFoundException;
19+
use OCA\TermsOfService\ResponseDefinitions;
20+
use OCP\AppFramework\Http\Attribute\PublicPage;
1921
use OCP\AppFramework\OCSController;
2022
use OCP\AppFramework\Http;
2123
use OCP\AppFramework\Http\DataResponse;
@@ -26,6 +28,10 @@
2628
use OCA\TermsOfService\Events\TermsCreatedEvent;
2729
use OCP\EventDispatcher\IEventDispatcher;
2830

31+
/**
32+
* @psalm-import-type TermsOfServiceAdminFormData from ResponseDefinitions
33+
* @psalm-import-type TermsOfServiceTerms from ResponseDefinitions
34+
*/
2935
class TermsController extends OCSController {
3036
/** @var IFactory */
3137
private $factory;
@@ -73,9 +79,13 @@ public function __construct(string $appName,
7379
}
7480

7581
/**
76-
* @PublicPage
77-
* @return DataResponse
82+
* Get all available terms for the current country
83+
*
84+
* @return DataResponse<Http::STATUS_OK, array{terms: list<TermsOfServiceTerms>, languages: array<string, string>, hasSigned: bool}, array{}>
85+
*
86+
* 200: Get list successfully
7887
*/
88+
#[PublicPage]
7989
public function index(): DataResponse {
8090
$currentCountry = $this->countryDetector->getCountry();
8191
$countryTerms = $this->termsMapper->getTermsForCountryCode($currentCountry);
@@ -86,30 +96,46 @@ public function index(): DataResponse {
8696
}
8797

8898
$response = [
89-
'terms' => $countryTerms,
99+
'terms' => array_map(static fn(Terms $terms): array => $terms->jsonSerialize(), $countryTerms),
90100
'languages' => $this->languageMapper->getLanguages(),
91101
'hasSigned' => $this->checker->currentUserHasSigned(),
92102
];
93103
return new DataResponse($response);
94104
}
95105

96106
/**
97-
* @return DataResponse
107+
* Get the form data for the admin interface
108+
*
109+
* @return DataResponse<Http::STATUS_OK, TermsOfServiceAdminFormData, array{}>
110+
*
111+
* 200: Get form data successfully
98112
*/
99113
public function getAdminFormData(): DataResponse {
114+
$forPublicShares = $this->config->getAppValue(Application::APPNAME, 'tos_on_public_shares', '0');
115+
if ($forPublicShares !== '0') {
116+
$forPublicShares = '1';
117+
}
118+
$forUsers = $this->config->getAppValue(Application::APPNAME, 'tos_for_users', '1');
119+
if ($forUsers !== '1') {
120+
$forUsers = '0';
121+
}
100122
$response = [
101-
'terms' => $this->termsMapper->getTerms(),
123+
'terms' => array_map(static fn(Terms $terms): array => $terms->jsonSerialize(), $this->termsMapper->getTerms()),
102124
'countries' => $this->countryMapper->getCountries(),
103125
'languages' => $this->languageMapper->getLanguages(),
104-
'tos_on_public_shares' => $this->config->getAppValue(Application::APPNAME, 'tos_on_public_shares', '0'),
105-
'tos_for_users' => $this->config->getAppValue(Application::APPNAME, 'tos_for_users', '1'),
126+
'tos_on_public_shares' => $forPublicShares,
127+
'tos_for_users' => $forUsers,
106128
];
107129
return new DataResponse($response);
108130
}
109131

110132
/**
111-
* @param int $id
112-
* @return DataResponse
133+
* Delete a given Term by id
134+
*
135+
* @param positive-int $id The terms which should be deleted
136+
* @return DataResponse<Http::STATUS_OK, array<empty>, array{}>
137+
*
138+
* 200: Deleted successfully
113139
*/
114140
public function destroy(int $id): DataResponse {
115141
$terms = new Terms();
@@ -120,15 +146,21 @@ public function destroy(int $id): DataResponse {
120146

121147
return new DataResponse();
122148
}
149+
123150
protected function createTermsCreatedEvent(): TermsCreatedEvent {
124151
return new TermsCreatedEvent();
125152
}
126153

127154
/**
128-
* @param string $countryCode
129-
* @param string $languageCode
130-
* @param string $body
131-
* @return DataResponse
155+
* Create new terms
156+
*
157+
* @param string $countryCode One of the 2-letter region codes or `--` for "global"
158+
* @param string $languageCode One of the 2-letter language codes
159+
* @param string $body The actual terms and conditions text (can be markdown, using headers, basic text formating, lists and links)
160+
* @return DataResponse<Http::STATUS_OK, TermsOfServiceTerms, array{}>|DataResponse<Http::STATUS_EXPECTATION_FAILED, array<empty>, array{}>
161+
*
162+
* 200: Created successfully
163+
* 417: Country or language code was not a valid option
132164
*/
133165
public function create(string $countryCode,
134166
string $languageCode,
@@ -161,6 +193,6 @@ public function create(string $countryCode,
161193
$event = $this->createTermsCreatedEvent();
162194
$this->eventDispatcher->dispatchTyped($event);
163195

164-
return new DataResponse($terms);
196+
return new DataResponse($terms->jsonSerialize());
165197
}
166198
}

lib/Db/Entities/Terms.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
namespace OCA\TermsOfService\Db\Entities;
88

9+
use OCA\TermsOfService\ResponseDefinitions;
910
use OCP\AppFramework\Db\Entity;
1011

1112
/**
@@ -15,6 +16,8 @@
1516
* @method void setLanguageCode(string $languageCode)
1617
* @method string getBody()
1718
* @method void setBody(string $body)
19+
*
20+
* @psalm-import-type TermsOfServiceTerms from ResponseDefinitions
1821
*/
1922
class Terms extends Entity implements \JsonSerializable {
2023
/** @var string */

lib/Db/Mapper/CountryMapper.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public function isValidCountry($countryCode): bool {
3232
/**
3333
* Gets the countries as well as the two-letter code
3434
*
35-
* @return array
35+
* @return array<string, string>
3636
*/
3737
public function getCountries(): array {
3838
$countries = [

lib/Db/Mapper/LanguageMapper.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ public function isValidLanguage($languageCode): bool {
2626
return isset($this->getLanguages()[$languageCode]);
2727
}
2828

29+
/**
30+
* @return array<string, string>
31+
*/
2932
public function getLanguages(): array {
3033
$mappedLanguages = [
3134
'aa' => $this->l->t('Afar'),

lib/Db/Mapper/TermsMapper.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public function __construct(IDBConnection $db) {
2727
* Returns all terms and conditions for the country code
2828
*
2929
* @param string $countryCode
30-
* @return Terms[]
30+
* @return list<Terms>
3131
*/
3232
public function getTermsForCountryCode(string $countryCode): array {
3333
$query = $this->db->getQueryBuilder();
@@ -81,7 +81,7 @@ public function getTermsForCountryCodeAndLanguageCode(string $countryCode, strin
8181
/**
8282
* Returns all terms and conditions
8383
*
84-
* @return Terms[]
84+
* @return array<string, Terms>
8585
*/
8686
public function getTerms(): array {
8787
$query = $this->db->getQueryBuilder();
@@ -91,7 +91,7 @@ public function getTerms(): array {
9191
$entities = [];
9292
$result = $query->execute();
9393
while ($row = $result->fetch()){
94-
$entities[(int) $row['id']] = $this->mapRowToEntity($row);
94+
$entities[$row['id']] = $this->mapRowToEntity($row);
9595
}
9696
$result->closeCursor();
9797

0 commit comments

Comments
 (0)