Skip to content

Commit

Permalink
chore: add basics for openapi extractor
Browse files Browse the repository at this point in the history
Signed-off-by: Christian Hartmann <chris-hartmann@gmx.de>
  • Loading branch information
Chartman123 committed Oct 26, 2024
1 parent 14e181e commit 2bcf91b
Show file tree
Hide file tree
Showing 7 changed files with 374 additions and 33 deletions.
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 <blizzz@arthur-schiwon.de>
# 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 `\" ]] || (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
exit 1 # make it red to grab attention
8 changes: 3 additions & 5 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,15 @@
}
},
"scripts": {
"post-install-cmd": [
"@composer bin psalm install --ansi",
"@composer bin cs-fixer install --ansi"
],
"post-install-cmd": "@composer bin all install --ansi",
"bin": "echo 'bin not installed'",
"cs:fix": "php-cs-fixer fix",
"cs:check": "php-cs-fixer fix --dry-run --diff",
"lint": "find . -name \\*.php -not -path './vendor*/*' -print0 | xargs -0 -n1 php -l",
"test:unit": "phpunit -c tests/phpunit.xml",
"test:integration": "phpunit -c tests/phpunit.integration.xml",
"psalm": "psalm"
"psalm": "psalm",
"openapi": "generate-spec"
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.8",
Expand Down
50 changes: 25 additions & 25 deletions lib/Controller/ApiController.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,14 @@

use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\IMapperException;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\ApiRoute;
use OCP\AppFramework\Http\Attribute\CORS;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\Attribute\PublicPage;
use OCP\AppFramework\Http\DataDownloadResponse;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Http\Response;
use OCP\AppFramework\OCS\OCSBadRequestException;
use OCP\AppFramework\OCS\OCSForbiddenException;
use OCP\AppFramework\OCS\OCSNotFoundException;
Expand Down Expand Up @@ -104,7 +104,7 @@ public function __construct(
/**
* Handle CORS options request by calling parent function
*/
#[ApiRoute(verb: 'OPTIONS', url: Constants::API_BASE . '{path}', requirements: Constants::API_V3_REQUIREMENTS)]
#[ApiRoute(verb: 'OPTIONS', url: '/api/v3/{path}', requirements: ['path' => '.+'])]
public function preflightedCors() {
parent::preflightedCors();
}
Expand All @@ -118,7 +118,7 @@ public function preflightedCors() {
*/
#[CORS()]
#[NoAdminRequired()]
#[ApiRoute(verb: 'GET', url: Constants::API_BASE . 'forms', requirements: Constants::API_V3_REQUIREMENTS)]
#[ApiRoute(verb: 'GET', url: '/api/v3/forms')]
public function getForms(string $type = 'owned'): DataResponse {
if ($type === 'owned') {
$forms = $this->formMapper->findAllByOwnerId($this->currentUser->getUID());
Expand Down Expand Up @@ -147,7 +147,7 @@ public function getForms(string $type = 'owned'): DataResponse {
*/
#[CORS()]
#[NoAdminRequired()]
#[ApiRoute(verb: 'POST', url: Constants::API_BASE . 'forms', requirements: Constants::API_V3_REQUIREMENTS)]
#[ApiRoute(verb: 'POST', url: '/api/v3/forms')]
public function newForm(?int $fromId = null): DataResponse {
// Check if user is allowed
if (!$this->configService->canCreateForms()) {
Expand Down Expand Up @@ -222,7 +222,7 @@ public function newForm(?int $fromId = null): DataResponse {
*/
#[CORS()]
#[NoAdminRequired()]
#[ApiRoute(verb: 'GET', url: Constants::API_BASE . 'forms/{formId}', requirements: Constants::API_V3_REQUIREMENTS)]
#[ApiRoute(verb: 'GET', url: '/api/v3/forms/{formId}')]
public function getForm(int $formId): DataResponse {
try {
$form = $this->formMapper->findById($formId);
Expand Down Expand Up @@ -252,7 +252,7 @@ public function getForm(int $formId): DataResponse {
*/
#[CORS()]
#[NoAdminRequired()]
#[ApiRoute(verb: 'PATCH', url: Constants::API_BASE . 'forms/{formId}', requirements: Constants::API_V3_REQUIREMENTS)]
#[ApiRoute(verb: 'PATCH', url: '/api/v3/forms/{formId}')]
public function updateForm(int $formId, array $keyValuePairs): DataResponse {
$this->logger->debug('Updating form: formId: {formId}, values: {keyValuePairs}', [
'formId' => $formId,
Expand Down Expand Up @@ -339,7 +339,7 @@ public function updateForm(int $formId, array $keyValuePairs): DataResponse {
*/
#[CORS()]
#[NoAdminRequired()]
#[ApiRoute(verb: 'DELETE', url: Constants::API_BASE . 'forms/{formId}', requirements: Constants::API_V3_REQUIREMENTS)]
#[ApiRoute(verb: 'DELETE', url: '/api/v3/forms/{formId}')]
public function deleteForm(int $formId): DataResponse {
$this->logger->debug('Delete Form: {formId}', [
'formId' => $formId,
Expand All @@ -362,7 +362,7 @@ public function deleteForm(int $formId): DataResponse {
*/
#[CORS()]
#[NoAdminRequired()]
#[ApiRoute(verb: 'GET', url: Constants::API_BASE . 'forms/{formId}/questions', requirements: Constants::API_V3_REQUIREMENTS)]
#[ApiRoute(verb: 'GET', url: '/api/v3/forms/{formId}/questions')]
public function getQuestions(int $formId): DataResponse {
try {
$form = $this->formMapper->findById($formId);
Expand Down Expand Up @@ -392,7 +392,7 @@ public function getQuestions(int $formId): DataResponse {
*/
#[CORS()]
#[NoAdminRequired()]
#[ApiRoute(verb: 'GET', url: Constants::API_BASE . 'forms/{formId}/questions/{questionId}', requirements: Constants::API_V3_REQUIREMENTS)]
#[ApiRoute(verb: 'GET', url: '/api/v3/forms/{formId}/questions/{questionId}')]
public function getQuestion(int $formId, int $questionId): DataResponse {
try {
$form = $this->formMapper->findById($formId);
Expand Down Expand Up @@ -428,7 +428,7 @@ public function getQuestion(int $formId, int $questionId): DataResponse {
*/
#[CORS()]
#[NoAdminRequired()]
#[ApiRoute(verb: 'POST', url: Constants::API_BASE . 'forms/{formId}/questions', requirements: Constants::API_V3_REQUIREMENTS)]
#[ApiRoute(verb: 'POST', url: '/api/v3/forms/{formId}/questions')]
public function newQuestion(int $formId, ?string $type = null, string $text = '', ?int $fromId = null): DataResponse {
$form = $this->getFormIfAllowed($formId);
if ($this->formsService->isFormArchived($form)) {
Expand Down Expand Up @@ -534,7 +534,7 @@ public function newQuestion(int $formId, ?string $type = null, string $text = ''
*/
#[CORS()]
#[NoAdminRequired()]
#[ApiRoute(verb: 'PATCH', url: Constants::API_BASE . 'forms/{formId}/questions/{questionId}', requirements: Constants::API_V3_REQUIREMENTS)]
#[ApiRoute(verb: 'PATCH', url: '/api/v3/forms/{formId}/questions/{questionId}')]
public function updateQuestion(int $formId, int $questionId, array $keyValuePairs): DataResponse {
$this->logger->debug('Updating question: formId: {formId}, questionId: {questionId}, values: {keyValuePairs}', [
'formId' => $formId,
Expand Down Expand Up @@ -603,7 +603,7 @@ public function updateQuestion(int $formId, int $questionId, array $keyValuePair
*/
#[CORS()]
#[NoAdminRequired()]
#[ApiRoute(verb: 'DELETE', url: Constants::API_BASE . 'forms/{formId}/questions/{questionId}', requirements: Constants::API_V3_REQUIREMENTS)]
#[ApiRoute(verb: 'DELETE', url: '/api/v3/forms/{formId}/questions/{questionId}')]
public function deleteQuestion(int $formId, int $questionId): DataResponse {
$this->logger->debug('Mark question as deleted: {questionId}', [
'questionId' => $questionId,
Expand Down Expand Up @@ -659,7 +659,7 @@ public function deleteQuestion(int $formId, int $questionId): DataResponse {
*/
#[CORS()]
#[NoAdminRequired()]
#[ApiRoute(verb: 'PATCH', url: Constants::API_BASE . 'forms/{formId}/questions', requirements: Constants::API_V3_REQUIREMENTS)]
#[ApiRoute(verb: 'PATCH', url: '/api/v3/forms/{formId}/questions')]
public function reorderQuestions(int $formId, array $newOrder): DataResponse {
$this->logger->debug('Reordering Questions on Form {formId} as Question-Ids {newOrder}', [
'formId' => $formId,
Expand Down Expand Up @@ -751,7 +751,7 @@ public function reorderQuestions(int $formId, array $newOrder): DataResponse {
*/
#[CORS()]
#[NoAdminRequired()]
#[ApiRoute(verb: 'POST', url: Constants::API_BASE . 'forms/{formId}/questions/{questionId}/options', requirements: Constants::API_V3_REQUIREMENTS)]
#[ApiRoute(verb: 'POST', url: '/api/v3/forms/{formId}/questions/{questionId}/options')]
public function newOption(int $formId, int $questionId, array $optionTexts): DataResponse {
$this->logger->debug('Adding new options: formId: {formId}, questionId: {questionId}, text: {text}', [
'formId' => $formId,
Expand Down Expand Up @@ -824,7 +824,7 @@ public function newOption(int $formId, int $questionId, array $optionTexts): Dat
*/
#[CORS()]
#[NoAdminRequired()]
#[ApiRoute(verb: 'PATCH', url: Constants::API_BASE . 'forms/{formId}/questions/{questionId}/options/{optionId}', requirements: Constants::API_V3_REQUIREMENTS)]
#[ApiRoute(verb: 'PATCH', url: '/api/v3/forms/{formId}/questions/{questionId}/options/{optionId}')]
public function updateOption(int $formId, int $questionId, int $optionId, array $keyValuePairs): DataResponse {
$this->logger->debug('Updating option: form: {formId}, question: {questionId}, option: {optionId}, values: {keyValuePairs}', [
'formId' => $formId,
Expand Down Expand Up @@ -888,7 +888,7 @@ public function updateOption(int $formId, int $questionId, int $optionId, array
*/
#[CORS()]
#[NoAdminRequired()]
#[ApiRoute(verb: 'DELETE', url: Constants::API_BASE . 'forms/{formId}/questions/{questionId}/options/{optionId}', requirements: Constants::API_V3_REQUIREMENTS)]
#[ApiRoute(verb: 'DELETE', url: '/api/v3/forms/{formId}/questions/{questionId}/options/{optionId}')]
public function deleteOption(int $formId, int $questionId, int $optionId): DataResponse {
$this->logger->debug('Deleting option: {optionId}', [
'optionId' => $optionId
Expand Down Expand Up @@ -936,7 +936,7 @@ public function deleteOption(int $formId, int $questionId, int $optionId): DataR
*/
#[CORS()]
#[NoAdminRequired()]
#[ApiRoute(verb: 'PATCH', url: Constants::API_BASE . 'forms/{formId}/questions/{questionId}/options', requirements: Constants::API_V3_REQUIREMENTS)]
#[ApiRoute(verb: 'PATCH', url: '/api/v3/forms/{formId}/questions/{questionId}/options')]
public function reorderOptions(int $formId, int $questionId, array $newOrder) {
$form = $this->getFormIfAllowed($formId);
if ($this->formsService->isFormArchived($form)) {
Expand Down Expand Up @@ -1027,7 +1027,7 @@ public function reorderOptions(int $formId, int $questionId, array $newOrder) {
*/
#[CORS()]
#[NoAdminRequired()]
#[ApiRoute(verb: 'GET', url: Constants::API_BASE . 'forms/{formId}/submissions', requirements: Constants::API_V3_REQUIREMENTS)]
#[ApiRoute(verb: 'GET', url: '/api/v3/forms/{formId}/submissions')]
public function getSubmissions(int $formId, ?string $fileFormat = null): DataResponse|DataDownloadResponse {
try {
$form = $this->formMapper->findById($formId);
Expand Down Expand Up @@ -1088,7 +1088,7 @@ public function getSubmissions(int $formId, ?string $fileFormat = null): DataRes
*/
#[CORS()]
#[NoAdminRequired()]
#[ApiRoute(verb: 'DELETE', url: Constants::API_BASE . 'forms/{formId}/submissions', requirements: Constants::API_V3_REQUIREMENTS)]
#[ApiRoute(verb: 'DELETE', url: '/api/v3/forms/{formId}/submissions')]
public function deleteAllSubmissions(int $formId): DataResponse {
$this->logger->debug('Delete all submissions to form: {formId}', [
'formId' => $formId,
Expand Down Expand Up @@ -1128,7 +1128,7 @@ public function deleteAllSubmissions(int $formId): DataResponse {
#[NoAdminRequired()]
#[NoCSRFRequired()]
#[PublicPage()]
#[ApiRoute(verb: 'POST', url: Constants::API_BASE . 'forms/{formId}/submissions', requirements: Constants::API_V3_REQUIREMENTS)]
#[ApiRoute(verb: 'POST', url: '/api/v3/forms/{formId}/submissions')]
public function newSubmission(int $formId, array $answers, string $shareHash = ''): DataResponse {
$this->logger->debug('Inserting submission: formId: {formId}, answers: {answers}, shareHash: {shareHash}', [
'formId' => $formId,
Expand Down Expand Up @@ -1221,7 +1221,7 @@ public function newSubmission(int $formId, array $answers, string $shareHash = '
*/
#[CORS()]
#[NoAdminRequired()]
#[ApiRoute(verb: 'DELETE', url: Constants::API_BASE . 'forms/{formId}/submissions/{submissionId}', requirements: Constants::API_V3_REQUIREMENTS)]
#[ApiRoute(verb: 'DELETE', url: '/api/v3/forms/{formId}/submissions/{submissionId}')]
public function deleteSubmission(int $formId, int $submissionId): DataResponse {
$this->logger->debug('Delete Submission: {submissionId}', [
'submissionId' => $submissionId,
Expand Down Expand Up @@ -1265,7 +1265,7 @@ public function deleteSubmission(int $formId, int $submissionId): DataResponse {
*/
#[CORS()]
#[NoAdminRequired()]
#[ApiRoute(verb: 'POST', url: Constants::API_BASE . 'forms/{formId}/submissions/export', requirements: Constants::API_V3_REQUIREMENTS)]
#[ApiRoute(verb: 'POST', url: '/api/v3/forms/{formId}/submissions/export')]
public function exportSubmissionsToCloud(int $formId, string $path, string $fileFormat = Constants::DEFAULT_FILE_FORMAT) {
try {
$form = $this->formMapper->findById($formId);
Expand All @@ -1290,13 +1290,13 @@ public function exportSubmissionsToCloud(int $formId, string $path, string $file
* @param int $formId id of the form
* @param int $questionId id of the question
* @param string $shareHash hash of the form share
* @return Response
* @return DataResponse
*/
#[CORS()]
#[NoAdminRequired()]
#[PublicPage()]
#[ApiRoute(verb: 'POST', url: Constants::API_BASE . 'forms/{formId}/submissions/files/{questionId}', requirements: Constants::API_V3_REQUIREMENTS)]
public function uploadFiles(int $formId, int $questionId, string $shareHash = ''): Response {
#[ApiRoute(verb: 'POST', url: '/api/v3/forms/{formId}/submissions/files/{questionId}')]
public function uploadFiles(int $formId, int $questionId, string $shareHash = ''): DataResponse {
$this->logger->debug('Uploading files for formId: {formId}, questionId: {questionId}', [
'formId' => $formId,
'questionId' => $questionId
Expand Down
6 changes: 3 additions & 3 deletions lib/Controller/ShareApiController.php
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ public function __construct(
*/
#[CORS()]
#[NoAdminRequired()]
#[ApiRoute(verb: 'POST', url: Constants::API_BASE . 'forms/{formId}/shares', requirements: Constants::API_V3_REQUIREMENTS)]
#[ApiRoute(verb: 'POST', url: '/api/v3/forms/{formId}/shares')]
public function newShare(int $formId, int $shareType, string $shareWith = '', array $permissions = [Constants::PERMISSION_SUBMIT]): DataResponse {
$this->logger->debug('Adding new share: formId: {formId}, shareType: {shareType}, shareWith: {shareWith}, permissions: {permissions}', [
'formId' => $formId,
Expand Down Expand Up @@ -217,7 +217,7 @@ public function newShare(int $formId, int $shareType, string $shareWith = '', ar
*/
#[CORS()]
#[NoAdminRequired()]
#[ApiRoute(verb: 'PATCH', url: Constants::API_BASE . 'forms/{formId}/shares/{shareId}', requirements: Constants::API_V3_REQUIREMENTS)]
#[ApiRoute(verb: 'PATCH', url: '/api/v3/forms/{formId}/shares/{shareId}')]
public function updateShare(int $formId, int $shareId, array $keyValuePairs): DataResponse {
$this->logger->debug('Updating share: {shareId} of form {formId}, permissions: {permissions}', [
'formId' => $formId,
Expand Down Expand Up @@ -308,7 +308,7 @@ public function updateShare(int $formId, int $shareId, array $keyValuePairs): Da
*/
#[CORS()]
#[NoAdminRequired()]
#[ApiRoute(verb: 'DELETE', url: Constants::API_BASE . 'forms/{formId}/shares/{shareId}', requirements: Constants::API_V3_REQUIREMENTS)]
#[ApiRoute(verb: 'DELETE', url: '/api/v3/forms/{formId}/shares/{shareId}')]
public function deleteShare(int $formId, int $shareId): DataResponse {
$this->logger->debug('Deleting share: {shareId} of form {formId}', [
'formId' => $formId,
Expand Down
4 changes: 4 additions & 0 deletions psalm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
</ignoreFiles>
</extraFiles>
<issueHandlers>
<LessSpecificReturnStatement errorLevel="error"/>
<LessSpecificReturnType errorLevel="error"/>
<LessSpecificImplementedReturnType errorLevel="error"/>
<MoreSpecificReturnType errorLevel="error"/>
<UndefinedClass>
<errorLevel type="suppress">
<referencedClass name="OCA\Circles\CirclesManager" />
Expand Down
5 changes: 5 additions & 0 deletions vendor-bin/openapi-extractor/composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"require-dev": {
"nextcloud/openapi-extractor": "^1.0"
}
}
Loading

0 comments on commit 2bcf91b

Please sign in to comment.