Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add OpenAPI docs #1011

Merged
merged 5 commits into from
Oct 28, 2024
Merged
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
94 changes: 94 additions & 0 deletions .github/workflows/openapi.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# This workflow is provided via the organization template repository
#
# https://github.com/nextcloud/.github
# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
#
# SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
# SPDX-FileCopyrightText: 2024 Arthur Schiwon <[email protected]>
# SPDX-License-Identifier: MIT

name: OpenAPI

on: pull_request

permissions:
contents: read

concurrency:
group: openapi-${{ github.head_ref || github.run_id }}
cancel-in-progress: true

jobs:
openapi:
runs-on: ubuntu-latest

if: ${{ github.repository_owner != 'nextcloud-gmbh' }}

steps:
- name: Checkout
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7

- name: Get php version
id: php_versions
uses: icewind1991/nextcloud-version-matrix@58becf3b4bb6dc6cef677b15e2fd8e7d48c0908f # v1.3.1

- name: Set up php
uses: shivammathur/setup-php@c541c155eee45413f5b09a52248675b1a2575231 # v2.31.1
with:
php-version: ${{ steps.php_versions.outputs.php-available }}
extensions: xml
coverage: none
ini-file: development
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Check Typescript OpenApi types
id: check_typescript_openapi
uses: andstor/file-existence-action@076e0072799f4942c8bc574a82233e1e4d13e9d6 # v3.0.0
with:
files: "src/types/openapi/openapi*.ts"

- name: Read package.json node and npm engines version
if: steps.check_typescript_openapi.outputs.files_exists == 'true'
uses: skjnldsv/read-package-engines-version-actions@06d6baf7d8f41934ab630e97d9e6c0bc9c9ac5e4 # v3
id: node_versions
# Continue if no package.json
continue-on-error: true
with:
fallbackNode: '^20'
fallbackNpm: '^10'

- name: Set up node ${{ steps.node_versions.outputs.nodeVersion }}
if: ${{ steps.node_versions.outputs.nodeVersion }}
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
with:
node-version: ${{ steps.node_versions.outputs.nodeVersion }}

- name: Set up npm ${{ steps.node_versions.outputs.npmVersion }}
if: ${{ steps.node_versions.outputs.nodeVersion }}
run: npm i -g 'npm@${{ steps.node_versions.outputs.npmVersion }}'

- name: Install dependencies & build
if: ${{ steps.node_versions.outputs.nodeVersion }}
env:
CYPRESS_INSTALL_BINARY: 0
PUPPETEER_SKIP_DOWNLOAD: true
run: |
npm ci

- name: Set up dependencies
run: composer i

- name: Regenerate OpenAPI
run: composer run openapi

- name: Check openapi*.json and typescript changes
run: |
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)"

- name: Show changes on failure
if: failure()
run: |
git status
git --no-pager diff openapi*
exit 1 # make it red to grab attention
6 changes: 6 additions & 0 deletions REUSE.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,9 @@ path = ["vendor/geoip2/**", "vendor/GeoLite2-Country.mmdb"]
precedence = "aggregate"
SPDX-FileCopyrightText = "geoip2 contributors"
SPDX-License-Identifier = "Apache-2.0"

[[annotations]]
path = ["openapi.json", "openapi-**.json"]
precedence = "aggregate"
SPDX-FileCopyrightText = "2024 Nextcloud GmbH and Nextcloud contributors"
SPDX-License-Identifier = "AGPL-3.0-or-later"
20 changes: 15 additions & 5 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,22 @@
*/

return [
'ocs-resources' => [
'terms' => [
'url' => '/terms'
],
],
'ocs' => [
[
'name' => 'Terms#index',
'url' => '/terms',
'verb' => 'GET',
],
[
'name' => 'Terms#create',
'url' => '/terms',
'verb' => 'POST',
],
[
'name' => 'Terms#destroy',
'url' => '/terms/{id}',
'verb' => 'DELETE',
],
[
'name' => 'Terms#getAdminFormData',
'url' => '/terms/admin',
Expand Down
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"scripts": {
"bin": "echo 'bin not installed'",
"lint": "find . -name \\*.php -not -path './vendor/*' -print0 | xargs -0 -n1 php -l",
"openapi": "generate-spec --verbose",
"post-install-cmd": [
"@composer bin all install --ansi",
"composer dump-autoload"
Expand Down
29 changes: 21 additions & 8 deletions lib/Controller/SigningController.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
use OCA\TermsOfService\BackgroundJobs\CreateNotifications;
use OCA\TermsOfService\Db\Entities\Signatory;
use OCA\TermsOfService\Db\Mapper\SignatoryMapper;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\PublicPage;
use OCP\AppFramework\Http\Attribute\UseSession;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCSController;
use OCP\BackgroundJob\IJobList;
Expand Down Expand Up @@ -67,12 +71,14 @@ protected function resetAllSignaturesEvent(): SignaturesResetEvent {
}

/**
* @NoAdminRequired
* As a logged in user sign the terms
*
* @param int $termId
* @param int $termId The terms the user signed
* @return DataResponse<Http::STATUS_OK, array<empty>, array{}>
*
* @return DataResponse
* 200: Signed successfully
*/
#[NoAdminRequired]
public function signTerms(int $termId): DataResponse {
$signatory = new Signatory();
$signatory->setUserId($this->userId);
Expand All @@ -95,12 +101,15 @@ public function signTerms(int $termId): DataResponse {


/**
* @PublicPage
* As a guest sign the terms
*
* @param int $termId
* @UseSession
* @return DataResponse
* @param int $termId The terms the user signed
* @return DataResponse<Http::STATUS_OK, array<empty>, array{}>
*
* 200: Signed successfully
*/
#[PublicPage]
#[UseSession]
public function signTermsPublic(int $termId): DataResponse {
$uuid = $this->config->getAppValue(Application::APPNAME, 'term_uuid', '');
$this->session->set('term_uuid', $uuid);
Expand All @@ -110,7 +119,11 @@ public function signTermsPublic(int $termId): DataResponse {


/**
* @return DataResponse
* Reset the signatories of all accounts
*
* @return DataResponse<Http::STATUS_OK, array<empty>, array{}>
*
* 200: Reset successfully
*/
public function resetAllSignatories(): DataResponse {
$this->signatoryMapper->deleteAllSignatories();
Expand Down
60 changes: 46 additions & 14 deletions lib/Controller/TermsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
use OCA\TermsOfService\Db\Mapper\SignatoryMapper;
use OCA\TermsOfService\Db\Mapper\TermsMapper;
use OCA\TermsOfService\Exceptions\TermsNotFoundException;
use OCA\TermsOfService\ResponseDefinitions;
use OCP\AppFramework\Http\Attribute\PublicPage;
use OCP\AppFramework\OCSController;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
Expand All @@ -26,6 +28,10 @@
use OCA\TermsOfService\Events\TermsCreatedEvent;
use OCP\EventDispatcher\IEventDispatcher;

/**
* @psalm-import-type TermsOfServiceAdminFormData from ResponseDefinitions
* @psalm-import-type TermsOfServiceTerms from ResponseDefinitions
*/
class TermsController extends OCSController {
/** @var IFactory */
private $factory;
Expand Down Expand Up @@ -73,9 +79,13 @@ public function __construct(string $appName,
}

/**
* @PublicPage
* @return DataResponse
* Get all available terms for the current country
*
* @return DataResponse<Http::STATUS_OK, array{terms: list<TermsOfServiceTerms>, languages: array<string, string>, hasSigned: bool}, array{}>
*
* 200: Get list successfully
*/
#[PublicPage]
public function index(): DataResponse {
$currentCountry = $this->countryDetector->getCountry();
$countryTerms = $this->termsMapper->getTermsForCountryCode($currentCountry);
Expand All @@ -86,30 +96,46 @@ public function index(): DataResponse {
}

$response = [
'terms' => $countryTerms,
'terms' => array_map(static fn(Terms $terms): array => $terms->jsonSerialize(), $countryTerms),
'languages' => $this->languageMapper->getLanguages(),
'hasSigned' => $this->checker->currentUserHasSigned(),
];
return new DataResponse($response);
}

/**
* @return DataResponse
* Get the form data for the admin interface
*
* @return DataResponse<Http::STATUS_OK, TermsOfServiceAdminFormData, array{}>
*
* 200: Get form data successfully
*/
public function getAdminFormData(): DataResponse {
$forPublicShares = $this->config->getAppValue(Application::APPNAME, 'tos_on_public_shares', '0');
if ($forPublicShares !== '0') {
$forPublicShares = '1';
}
$forUsers = $this->config->getAppValue(Application::APPNAME, 'tos_for_users', '1');
if ($forUsers !== '1') {
$forUsers = '0';
}
$response = [
'terms' => $this->termsMapper->getTerms(),
'terms' => array_map(static fn(Terms $terms): array => $terms->jsonSerialize(), $this->termsMapper->getTerms()),
'countries' => $this->countryMapper->getCountries(),
'languages' => $this->languageMapper->getLanguages(),
'tos_on_public_shares' => $this->config->getAppValue(Application::APPNAME, 'tos_on_public_shares', '0'),
'tos_for_users' => $this->config->getAppValue(Application::APPNAME, 'tos_for_users', '1'),
'tos_on_public_shares' => $forPublicShares,
'tos_for_users' => $forUsers,
];
return new DataResponse($response);
}

/**
* @param int $id
* @return DataResponse
* Delete a given Term by id
*
* @param positive-int $id The terms which should be deleted
* @return DataResponse<Http::STATUS_OK, array<empty>, array{}>
*
* 200: Deleted successfully
*/
public function destroy(int $id): DataResponse {
$terms = new Terms();
Expand All @@ -120,15 +146,21 @@ public function destroy(int $id): DataResponse {

return new DataResponse();
}

protected function createTermsCreatedEvent(): TermsCreatedEvent {
return new TermsCreatedEvent();
}

/**
* @param string $countryCode
* @param string $languageCode
* @param string $body
* @return DataResponse
* Create new terms
*
* @param string $countryCode One of the 2-letter region codes or `--` for "global"
* @param string $languageCode One of the 2-letter language codes
* @param string $body The actual terms and conditions text (can be markdown, using headers, basic text formating, lists and links)
* @return DataResponse<Http::STATUS_OK, TermsOfServiceTerms, array{}>|DataResponse<Http::STATUS_EXPECTATION_FAILED, array<empty>, array{}>
*
* 200: Created successfully
* 417: Country or language code was not a valid option
*/
public function create(string $countryCode,
string $languageCode,
Expand Down Expand Up @@ -161,6 +193,6 @@ public function create(string $countryCode,
$event = $this->createTermsCreatedEvent();
$this->eventDispatcher->dispatchTyped($event);

return new DataResponse($terms);
return new DataResponse($terms->jsonSerialize());
}
}
3 changes: 3 additions & 0 deletions lib/Db/Entities/Terms.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

namespace OCA\TermsOfService\Db\Entities;

use OCA\TermsOfService\ResponseDefinitions;
use OCP\AppFramework\Db\Entity;

/**
Expand All @@ -15,6 +16,8 @@
* @method void setLanguageCode(string $languageCode)
* @method string getBody()
* @method void setBody(string $body)
*
* @psalm-import-type TermsOfServiceTerms from ResponseDefinitions
*/
class Terms extends Entity implements \JsonSerializable {
/** @var string */
Expand Down
2 changes: 1 addition & 1 deletion lib/Db/Mapper/CountryMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public function isValidCountry($countryCode): bool {
/**
* Gets the countries as well as the two-letter code
*
* @return array
* @return array<string, string>
*/
public function getCountries(): array {
$countries = [
Expand Down
3 changes: 3 additions & 0 deletions lib/Db/Mapper/LanguageMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ public function isValidLanguage($languageCode): bool {
return isset($this->getLanguages()[$languageCode]);
}

/**
* @return array<string, string>
*/
public function getLanguages(): array {
$mappedLanguages = [
'aa' => $this->l->t('Afar'),
Expand Down
6 changes: 3 additions & 3 deletions lib/Db/Mapper/TermsMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public function __construct(IDBConnection $db) {
* Returns all terms and conditions for the country code
*
* @param string $countryCode
* @return Terms[]
* @return list<Terms>
*/
public function getTermsForCountryCode(string $countryCode): array {
$query = $this->db->getQueryBuilder();
Expand Down Expand Up @@ -81,7 +81,7 @@ public function getTermsForCountryCodeAndLanguageCode(string $countryCode, strin
/**
* Returns all terms and conditions
*
* @return Terms[]
* @return array<string, Terms>
*/
public function getTerms(): array {
$query = $this->db->getQueryBuilder();
Expand All @@ -91,7 +91,7 @@ public function getTerms(): array {
$entities = [];
$result = $query->execute();
while ($row = $result->fetch()){
$entities[(int) $row['id']] = $this->mapRowToEntity($row);
$entities[$row['id']] = $this->mapRowToEntity($row);
}
$result->closeCursor();

Expand Down
Loading
Loading