From 19186ca1a18839bfb9263656e79abf9a2a8234ba Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Wed, 30 Sep 2026 10:17:05 -0300 Subject: [PATCH 01/16] fix: expose protocol report endpoint as plain JSON --- lib/Controller/ReportController.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Controller/ReportController.php b/lib/Controller/ReportController.php index 1a7aee0e..2d04959b 100644 --- a/lib/Controller/ReportController.php +++ b/lib/Controller/ReportController.php @@ -22,11 +22,11 @@ use OCP\AppFramework\Http\Attribute\OpenAPI; use OCP\AppFramework\Http\Attribute\PublicPage; use OCP\AppFramework\Http\DataResponse; -use OCP\AppFramework\OCSController; +use OCP\AppFramework\Controller; use OCP\IRequest; #[OpenAPI(tags: ['reports'])] -final class ReportController extends OCSController { +final class ReportController extends Controller { public function __construct( string $appName, IRequest $request, From e80d166592697583ef8051951c3d27e5fb962a4f Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Wed, 30 Sep 2026 10:17:10 -0300 Subject: [PATCH 02/16] test: cover direct protocol report responses --- .../features/api/usage_statistics.feature | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/integration/features/api/usage_statistics.feature b/tests/integration/features/api/usage_statistics.feature index 3594e27b..abbe653f 100644 --- a/tests/integration/features/api/usage_statistics.feature +++ b/tests/integration/features/api/usage_statistics.feature @@ -59,7 +59,7 @@ Feature: usage statistics OCS API | metrics | [{"category":"server","key":"version","type":"string","kind":"snapshot","aggregation":"distribution","description":"Application version","required":true},{"category":"usage","key":"requests_completed","type":"integer","kind":"period","aggregation":"numerical","description":"Completed requests","required":true}] | Then the response should have a status code 201 Given as anonymous user - When sending "post" to ocs "/apps/usage_statistics_server/api/v1/reports" + When sending "post" to "/apps/usage_statistics_server/api/v1/reports" | protocolVersion | 1 | | application | behat_report | | installationId | install-behat-report | @@ -69,8 +69,8 @@ Feature: usage statistics OCS API Then the response should have a status code 200 And the response should be a JSON array with the following mandatory values | key | value | - | (jq).ocs.data.status | accepted | - When sending "post" to ocs "/apps/usage_statistics_server/api/v1/reports" + | (jq).status | accepted | + When sending "post" to "/apps/usage_statistics_server/api/v1/reports" | protocolVersion | 1 | | application | behat_report | | installationId | install-behat-report | @@ -80,7 +80,7 @@ Feature: usage statistics OCS API Then the response should have a status code 200 And the response should be a JSON array with the following mandatory values | key | value | - | (jq).ocs.data.status | accepted | + | (jq).status | accepted | Scenario: report with an unknown metric is rejected Given as user "admin" @@ -90,7 +90,7 @@ Feature: usage statistics OCS API | metrics | [{"category":"server","key":"version","type":"string","kind":"snapshot","aggregation":"distribution","description":"Application version","required":true}] | Then the response should have a status code 201 Given as anonymous user - When sending "post" to ocs "/apps/usage_statistics_server/api/v1/reports" + When sending "post" to "/apps/usage_statistics_server/api/v1/reports" | protocolVersion | 1 | | application | behat_unknown_metric | | installationId | install-unknown-metric | @@ -100,7 +100,7 @@ Feature: usage statistics OCS API Then the response should have a status code 400 And the response should be a JSON array with the following mandatory values | key | value | - | (jq).ocs.data.error | invalid_report | + | (jq).error | invalid_report | Scenario: changing schema version for the same installation and reporting period conflicts Given as user "admin" @@ -115,7 +115,7 @@ Feature: usage statistics OCS API | metrics | [{"category":"server","key":"version","type":"string","kind":"snapshot","aggregation":"distribution","description":"Application version","required":true}] | Then the response should have a status code 201 Given as anonymous user - When sending "post" to ocs "/apps/usage_statistics_server/api/v1/reports" + When sending "post" to "/apps/usage_statistics_server/api/v1/reports" | protocolVersion | 1 | | application | behat_period_conflict | | installationId | install-period-conflict | @@ -123,7 +123,7 @@ Feature: usage statistics OCS API | period | {"start":"2026-08-01T00:00:00Z","end":"2026-09-01T00:00:00Z"} | | metrics | [{"category":"server","key":"version","type":"string","value":"1.0.0"}] | Then the response should have a status code 200 - When sending "post" to ocs "/apps/usage_statistics_server/api/v1/reports" + When sending "post" to "/apps/usage_statistics_server/api/v1/reports" | protocolVersion | 1 | | application | behat_period_conflict | | installationId | install-period-conflict | @@ -133,7 +133,7 @@ Feature: usage statistics OCS API Then the response should have a status code 409 And the response should be a JSON array with the following mandatory values | key | value | - | (jq).ocs.data.error | conflicting_report | + | (jq).error | conflicting_report | Scenario: administrator can read current aggregates Given as user "admin" @@ -143,7 +143,7 @@ Feature: usage statistics OCS API | metrics | [{"category":"server","key":"version","type":"string","kind":"snapshot","aggregation":"distribution","description":"Application version","required":true},{"category":"usage","key":"requests_completed","type":"integer","kind":"period","aggregation":"numerical","description":"Completed requests","required":true}] | Then the response should have a status code 201 Given as anonymous user - When sending "post" to ocs "/apps/usage_statistics_server/api/v1/reports" + When sending "post" to "/apps/usage_statistics_server/api/v1/reports" | protocolVersion | 1 | | application | behat_statistics | | installationId | install-behat-statistics | From 64cae0786bde4e671652c5d45d048e1ebc6bfeb9 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Wed, 30 Sep 2026 10:19:11 -0300 Subject: [PATCH 03/16] fix: route protocol ingestion outside OCS --- lib/Controller/ReportController.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Controller/ReportController.php b/lib/Controller/ReportController.php index 2d04959b..ca2e9f61 100644 --- a/lib/Controller/ReportController.php +++ b/lib/Controller/ReportController.php @@ -17,7 +17,7 @@ use OCA\UsageStatisticsServer\Service\SchemaValidator; use OCP\AppFramework\Http; use OCP\AppFramework\Http\Attribute\AnonRateLimit; -use OCP\AppFramework\Http\Attribute\ApiRoute; +use OCP\AppFramework\Http\Attribute\FrontpageRoute; use OCP\AppFramework\Http\Attribute\NoCSRFRequired; use OCP\AppFramework\Http\Attribute\OpenAPI; use OCP\AppFramework\Http\Attribute\PublicPage; @@ -60,7 +60,7 @@ public function __construct( #[PublicPage] #[NoCSRFRequired] #[AnonRateLimit(limit: 60, period: 3600)] - #[ApiRoute(verb: 'POST', url: '/api/{apiVersion}/reports', requirements: ['apiVersion' => '(v1)'])] + #[FrontpageRoute(verb: 'POST', url: '/api/{apiVersion}/reports', requirements: ['apiVersion' => '(v1)'])] public function create( int $protocolVersion, string $application, From 8fcc2f2ff50af49a35053fd1562f02aeac9c4456 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Wed, 30 Sep 2026 15:05:17 -0300 Subject: [PATCH 04/16] refactor: share report submission logic --- lib/Service/ReportSubmissionService.php | 47 +++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 lib/Service/ReportSubmissionService.php diff --git a/lib/Service/ReportSubmissionService.php b/lib/Service/ReportSubmissionService.php new file mode 100644 index 00000000..1854283b --- /dev/null +++ b/lib/Service/ReportSubmissionService.php @@ -0,0 +1,47 @@ + + * } $payload + * + * @throws InvalidReport + * @throws ConflictingReport + */ + public function submit(array $payload): void { + $report = $this->factory->fromPayload($payload); + $schema = $this->schemas->find($report->application, $report->schemaVersion); + if ($schema === null) { + throw new InvalidReport('Application schema is not registered.'); + } + + $this->schemaValidator->validateReport($report, $schema); + $this->repository->store($report); + } +} From 0b59c057353772a6d4caa371138b9082eecd5448 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Wed, 30 Sep 2026 15:05:21 -0300 Subject: [PATCH 05/16] refactor: delegate report submission --- lib/Controller/ReportController.php | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/lib/Controller/ReportController.php b/lib/Controller/ReportController.php index ca2e9f61..3570207a 100644 --- a/lib/Controller/ReportController.php +++ b/lib/Controller/ReportController.php @@ -9,12 +9,10 @@ namespace OCA\UsageStatisticsServer\Controller; -use OCA\UsageStatisticsServer\Db\ReportRepository; -use OCA\UsageStatisticsServer\Db\SchemaRepository; use OCA\UsageStatisticsServer\Service\ConflictingReport; use OCA\UsageStatisticsServer\Service\InvalidReport; -use OCA\UsageStatisticsServer\Service\ReportFactory; -use OCA\UsageStatisticsServer\Service\SchemaValidator; +use OCA\UsageStatisticsServer\Service\ReportSubmissionService; +use OCP\AppFramework\Controller; use OCP\AppFramework\Http; use OCP\AppFramework\Http\Attribute\AnonRateLimit; use OCP\AppFramework\Http\Attribute\FrontpageRoute; @@ -22,7 +20,6 @@ use OCP\AppFramework\Http\Attribute\OpenAPI; use OCP\AppFramework\Http\Attribute\PublicPage; use OCP\AppFramework\Http\DataResponse; -use OCP\AppFramework\Controller; use OCP\IRequest; #[OpenAPI(tags: ['reports'])] @@ -30,16 +27,13 @@ final class ReportController extends Controller { public function __construct( string $appName, IRequest $request, - private readonly ReportFactory $factory, - private readonly ReportRepository $repository, - private readonly SchemaRepository $schemas, - private readonly SchemaValidator $schemaValidator, + private readonly ReportSubmissionService $submission, ) { parent::__construct($appName, $request); } /** - * Submit a usage statistics report + * Submit a usage statistics report as plain Protocol v1 JSON * * Stores one validated report for one application installation and reporting period. * Repeating an already accepted report with the same schema version is idempotent. @@ -70,7 +64,7 @@ public function create( array $metrics, ): DataResponse { try { - $report = $this->factory->fromPayload([ + $this->submission->submit([ 'protocolVersion' => $protocolVersion, 'application' => $application, 'installationId' => $installationId, @@ -78,12 +72,6 @@ public function create( 'period' => $period, 'metrics' => $metrics, ]); - $schema = $this->schemas->find($report->application, $report->schemaVersion); - if ($schema === null) { - throw new InvalidReport('Application schema is not registered.'); - } - $this->schemaValidator->validateReport($report, $schema); - $this->repository->store($report); } catch (InvalidReport $e) { return new DataResponse(['error' => 'invalid_report', 'message' => $e->getMessage()], Http::STATUS_BAD_REQUEST); } catch (ConflictingReport $e) { From 7f1f65ae148e1e41821e0645cd45960d266055c9 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Wed, 30 Sep 2026 15:05:23 -0300 Subject: [PATCH 06/16] feat: expose OCS report ingestion endpoint --- lib/Controller/OcsReportController.php | 83 ++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 lib/Controller/OcsReportController.php diff --git a/lib/Controller/OcsReportController.php b/lib/Controller/OcsReportController.php new file mode 100644 index 00000000..1d94ec2d --- /dev/null +++ b/lib/Controller/OcsReportController.php @@ -0,0 +1,83 @@ + $metrics Aggregate metric values + * + * @return DataResponse|DataResponse + * + * 200: Report accepted + * 400: Invalid report + * 409: Conflicting report for the reporting period + */ + #[PublicPage] + #[NoCSRFRequired] + #[AnonRateLimit(limit: 60, period: 3600)] + #[ApiRoute(verb: 'POST', url: '/api/{apiVersion}/reports', requirements: ['apiVersion' => '(v1)'])] + public function create( + int $protocolVersion, + string $application, + string $installationId, + int $schemaVersion, + array $period, + array $metrics, + ): DataResponse { + try { + $this->submission->submit([ + 'protocolVersion' => $protocolVersion, + 'application' => $application, + 'installationId' => $installationId, + 'schemaVersion' => $schemaVersion, + 'period' => $period, + 'metrics' => $metrics, + ]); + } catch (InvalidReport $e) { + return new DataResponse(['error' => 'invalid_report', 'message' => $e->getMessage()], Http::STATUS_BAD_REQUEST); + } catch (ConflictingReport $e) { + return new DataResponse(['error' => 'conflicting_report', 'message' => $e->getMessage()], Http::STATUS_CONFLICT); + } + + return new DataResponse(['status' => 'accepted'], Http::STATUS_OK); + } +} From f85b1b704317e38fdc31155693eebaf180a70e1f Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Wed, 30 Sep 2026 15:06:26 -0300 Subject: [PATCH 07/16] test: cover OCS and plain report ingestion --- .../features/api/usage_statistics.feature | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/integration/features/api/usage_statistics.feature b/tests/integration/features/api/usage_statistics.feature index abbe653f..606da96d 100644 --- a/tests/integration/features/api/usage_statistics.feature +++ b/tests/integration/features/api/usage_statistics.feature @@ -82,6 +82,27 @@ Feature: usage statistics OCS API | key | value | | (jq).status | accepted | + + Scenario: anonymous report submission is also available through OCS + Given as user "admin" + When sending "post" to ocs "/apps/usage_statistics_server/api/v1/admin/schemas" + | application | behat_report_ocs | + | schemaVersion | 1 | + | metrics | [{"category":"server","key":"version","type":"string","kind":"snapshot","aggregation":"distribution","description":"Application version","required":true}] | + Then the response should have a status code 201 + Given as anonymous user + When sending "post" to ocs "/apps/usage_statistics_server/api/v1/reports" + | protocolVersion | 1 | + | application | behat_report_ocs | + | installationId | install-behat-report-ocs | + | schemaVersion | 1 | + | period | {"start":"2026-08-01T00:00:00Z","end":"2026-09-01T00:00:00Z"} | + | metrics | [{"category":"server","key":"version","type":"string","value":"1.0.0"}] | + Then the response should have a status code 200 + And the response should be a JSON array with the following mandatory values + | key | value | + | (jq).ocs.data.status | accepted | + Scenario: report with an unknown metric is rejected Given as user "admin" When sending "post" to ocs "/apps/usage_statistics_server/api/v1/admin/schemas" From 65964341901c272e5c3c21afacea37e054712baa Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Wed, 30 Sep 2026 15:06:39 -0300 Subject: [PATCH 08/16] docs: describe plain and OCS ingestion routes --- docs/protocol-v1.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/protocol-v1.md b/docs/protocol-v1.md index 7b608bc1..b9334613 100644 --- a/docs/protocol-v1.md +++ b/docs/protocol-v1.md @@ -21,6 +21,13 @@ Recommended endpoint shape: POST /api/v1/reports ``` +The reference Nextcloud implementation exposes two equivalent ingestion routes: + +- a plain HTTP/JSON route for protocol clients; +- an OCS route for Nextcloud-native tooling and API discovery. + +Both accept the same Protocol v1 request fields. The plain route returns the Protocol v1 response body directly. The OCS route wraps the same application-level response in the standard Nextcloud OCS envelope. OCS is an implementation detail of the Nextcloud reference server and is not required by Protocol v1. + The server SHOULD support idempotent resubmission of the same logical report. A successful submission, including an idempotent retry, returns the same application-level response: From 1fad7b4199eea6d994cc151627166b67891e1a01 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Wed, 30 Sep 2026 15:07:30 -0300 Subject: [PATCH 09/16] ci: enforce committed OpenAPI contracts --- .github/workflows/openapi.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/openapi.yml b/.github/workflows/openapi.yml index d8f2c92d..7daa86a4 100644 --- a/.github/workflows/openapi.yml +++ b/.github/workflows/openapi.yml @@ -56,7 +56,7 @@ jobs: run: npm i -g 'npm@${{ steps.node_versions.outputs.npmVersion }}' - name: Install JavaScript dependencies - run: npm install --no-package-lock + run: npm ci - name: Install Composer dependencies uses: ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda # v4.0.0 @@ -73,3 +73,12 @@ jobs: openapi-administration.json openapi-full.json src/types/openapi/*.ts + + - name: Check generated OpenAPI files + run: | + if [[ -n "$(git status --porcelain -- openapi.json openapi-administration.json openapi-full.json src/types/openapi)" ]]; then + echo 'Generated OpenAPI files are out of date. Run "composer run openapi" and commit the generated files.' + git status --short -- openapi.json openapi-administration.json openapi-full.json src/types/openapi + git --no-pager diff -- openapi.json openapi-administration.json openapi-full.json src/types/openapi + exit 1 + fi From 1f8c29b167b3c83625f7242f8a20fa66c3f96aca Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Wed, 30 Sep 2026 15:08:43 -0300 Subject: [PATCH 10/16] test: cover shared report submission service --- .../Service/ReportSubmissionServiceTest.php | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 tests/php/Integration/Service/ReportSubmissionServiceTest.php diff --git a/tests/php/Integration/Service/ReportSubmissionServiceTest.php b/tests/php/Integration/Service/ReportSubmissionServiceTest.php new file mode 100644 index 00000000..adc00e6a --- /dev/null +++ b/tests/php/Integration/Service/ReportSubmissionServiceTest.php @@ -0,0 +1,119 @@ +db = Server::get(IDBConnection::class); + $this->schemas = new SchemaRepository($this->db); + $this->submission = new ReportSubmissionService( + new ReportFactory(), + new ReportRepository($this->db), + $this->schemas, + new SchemaValidator(), + ); + $this->clearTables(); + } + + protected function tearDown(): void { + $this->clearTables(); + parent::tearDown(); + } + + public function testSubmitsReportAgainstRegisteredSchema(): void { + $definition = [ + 'application' => 'integration_app', + 'schemaVersion' => 1, + 'metrics' => [[ + 'category' => 'usage', + 'key' => 'requests_completed', + 'type' => 'integer', + 'kind' => 'period', + 'aggregation' => 'numerical', + 'description' => 'Completed requests', + 'required' => true, + ]], + ]; + $this->schemas->store('integration_app', 1, $definition); + + $this->submission->submit($this->payload()); + + self::assertSame(1, $this->countRows('usage_stats_reports')); + self::assertSame(1, $this->countRows('usage_stats_metrics')); + } + + public function testRejectsReportWhenSchemaIsNotRegistered(): void { + $this->expectException(InvalidReport::class); + $this->expectExceptionMessage('Application schema is not registered.'); + + $this->submission->submit($this->payload()); + } + + /** + * @return array{ + * protocolVersion:int, + * application:string, + * installationId:string, + * schemaVersion:int, + * period:array{start:string,end:string}, + * metrics:list + * } + */ + private function payload(): array { + return [ + 'protocolVersion' => 1, + 'application' => 'integration_app', + 'installationId' => 'integration-installation', + 'schemaVersion' => 1, + 'period' => [ + 'start' => '2026-08-01T00:00:00Z', + 'end' => '2026-09-01T00:00:00Z', + ], + 'metrics' => [[ + 'category' => 'usage', + 'key' => 'requests_completed', + 'type' => 'integer', + 'value' => 12, + ]], + ]; + } + + private function countRows(string $table): int { + return (int)$this->db->getQueryBuilder() + ->select($this->db->getQueryBuilder()->func()->count()) + ->from($table) + ->executeQuery() + ->fetchOne(); + } + + private function clearTables(): void { + $this->db->getQueryBuilder()->delete('usage_stats_metrics')->executeStatement(); + $this->db->getQueryBuilder()->delete('usage_stats_installations')->executeStatement(); + $this->db->getQueryBuilder()->delete('usage_stats_reports')->executeStatement(); + $this->db->getQueryBuilder()->delete('usage_stats_schemas')->executeStatement(); + } +} From 841f7a1d1aa71e05e85de7b192ff3a0136fa019c Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Wed, 30 Sep 2026 15:08:56 -0300 Subject: [PATCH 11/16] test: use one query builder per assertion --- .../php/Integration/Service/ReportSubmissionServiceTest.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/php/Integration/Service/ReportSubmissionServiceTest.php b/tests/php/Integration/Service/ReportSubmissionServiceTest.php index adc00e6a..2d32b7b4 100644 --- a/tests/php/Integration/Service/ReportSubmissionServiceTest.php +++ b/tests/php/Integration/Service/ReportSubmissionServiceTest.php @@ -103,8 +103,9 @@ private function payload(): array { } private function countRows(string $table): int { - return (int)$this->db->getQueryBuilder() - ->select($this->db->getQueryBuilder()->func()->count()) + $qb = $this->db->getQueryBuilder(); + return (int)$qb + ->select($qb->func()->count()) ->from($table) ->executeQuery() ->fetchOne(); From a58a5ab65b7380c592a1e8fc8f3822f6d6fe1f95 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Wed, 30 Sep 2026 15:15:37 -0300 Subject: [PATCH 12/16] ci: materialize generated OpenAPI contracts --- .github/workflows/openapi.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/openapi.yml b/.github/workflows/openapi.yml index 7daa86a4..4ce6652a 100644 --- a/.github/workflows/openapi.yml +++ b/.github/workflows/openapi.yml @@ -9,7 +9,7 @@ name: OpenAPI on: pull_request permissions: - contents: read + contents: write concurrency: group: openapi-${{ github.head_ref || github.run_id }} @@ -23,7 +23,7 @@ jobs: - name: Checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: - persist-credentials: false + persist-credentials: true submodules: true - name: Get php version @@ -74,11 +74,12 @@ jobs: openapi-full.json src/types/openapi/*.ts - - name: Check generated OpenAPI files + - name: Commit generated OpenAPI files run: | if [[ -n "$(git status --porcelain -- openapi.json openapi-administration.json openapi-full.json src/types/openapi)" ]]; then - echo 'Generated OpenAPI files are out of date. Run "composer run openapi" and commit the generated files.' - git status --short -- openapi.json openapi-administration.json openapi-full.json src/types/openapi - git --no-pager diff -- openapi.json openapi-administration.json openapi-full.json src/types/openapi - exit 1 + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add openapi.json openapi-administration.json openapi-full.json src/types/openapi + git commit -m "docs: update generated OpenAPI contracts" + git push origin HEAD:${GITHUB_HEAD_REF} fi From e8b1969bf66b4a6357d3ddaf0b39a0e31587767a Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Wed, 30 Sep 2026 15:17:47 -0300 Subject: [PATCH 13/16] ci: materialize OpenAPI from PR head --- .github/workflows/openapi.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/openapi.yml b/.github/workflows/openapi.yml index 4ce6652a..0040ee1a 100644 --- a/.github/workflows/openapi.yml +++ b/.github/workflows/openapi.yml @@ -25,6 +25,7 @@ jobs: with: persist-credentials: true submodules: true + ref: ${{ github.head_ref }} - name: Get php version id: php_versions From 5cb43fcb0cfa97d7b6effd50170dabadad946dbd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 30 Sep 2026 18:18:42 +0000 Subject: [PATCH 14/16] docs: update generated OpenAPI contracts --- openapi-administration.json | 1544 ++++++++++++++ openapi-full.json | 2000 +++++++++++++++++++ openapi.json | 508 +++++ src/types/openapi/openapi-administration.ts | 728 +++++++ src/types/openapi/openapi-full.ts | 944 +++++++++ src/types/openapi/openapi.ts | 243 +++ 6 files changed, 5967 insertions(+) create mode 100644 openapi-administration.json create mode 100644 openapi-full.json create mode 100644 openapi.json create mode 100644 src/types/openapi/openapi-administration.ts create mode 100644 src/types/openapi/openapi-full.ts create mode 100644 src/types/openapi/openapi.ts diff --git a/openapi-administration.json b/openapi-administration.json new file mode 100644 index 00000000..812b6f59 --- /dev/null +++ b/openapi-administration.json @@ -0,0 +1,1544 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "usage_statistics_server-administration", + "version": "0.0.1", + "description": "Receive and aggregate privacy-preserving usage statistics.", + "license": { + "name": "agpl" + } + }, + "components": { + "securitySchemes": { + "basic_auth": { + "type": "http", + "scheme": "basic" + }, + "bearer_auth": { + "type": "http", + "scheme": "bearer" + } + }, + "schemas": { + "OCSMeta": { + "type": "object", + "required": [ + "status", + "statuscode" + ], + "properties": { + "status": { + "type": "string" + }, + "statuscode": { + "type": "integer" + }, + "message": { + "type": "string" + }, + "totalitems": { + "type": "string" + }, + "itemsperpage": { + "type": "string" + } + } + } + } + }, + "paths": { + "/ocs/v2.php/apps/usage_statistics_server/api/{apiVersion}/admin/schemas": { + "post": { + "operationId": "schema-create", + "summary": "Register an application metric schema", + "description": "This endpoint requires admin access", + "tags": [ + "schema" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "application", + "schemaVersion", + "metrics" + ], + "properties": { + "application": { + "type": "string", + "description": "Stable application identifier" + }, + "schemaVersion": { + "type": "integer", + "format": "int64", + "description": "Immutable schema version" + }, + "metrics": { + "type": "array", + "description": "Metric definitions", + "items": { + "type": "object", + "required": [ + "category", + "key", + "type", + "kind", + "aggregation", + "description", + "required" + ], + "properties": { + "category": { + "type": "string" + }, + "key": { + "type": "string" + }, + "type": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "aggregation": { + "type": "string" + }, + "description": { + "type": "string" + }, + "required": { + "type": "boolean" + } + } + } + } + } + } + } + } + }, + "parameters": [ + { + "name": "apiVersion", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "v1" + ], + "default": "v1" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Schema was already registered with the same definition", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "application", + "schemaVersion", + "status" + ], + "properties": { + "application": { + "type": "string" + }, + "schemaVersion": { + "type": "integer", + "format": "int64" + }, + "status": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "201": { + "description": "Schema registered", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "application", + "schemaVersion", + "status" + ], + "properties": { + "application": { + "type": "string" + }, + "schemaVersion": { + "type": "integer", + "format": "int64" + }, + "status": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "400": { + "description": "Invalid schema definition", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "error", + "message" + ], + "properties": { + "error": { + "type": "string" + }, + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "409": { + "description": "Schema version already exists with another definition", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "error", + "message" + ], + "properties": { + "error": { + "type": "string" + }, + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/usage_statistics_server/api/{apiVersion}/admin/schemas/{application}/{schemaVersion}": { + "get": { + "operationId": "schema-get", + "summary": "Get one registered application metric schema", + "description": "This endpoint requires admin access", + "tags": [ + "schema" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "apiVersion", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "v1" + ], + "default": "v1" + } + }, + { + "name": "application", + "in": "path", + "description": "Stable application identifier", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "schemaVersion", + "in": "path", + "description": "Schema version", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Registered schema definition", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "additionalProperties": { + "type": "object" + } + } + } + } + } + } + } + } + }, + "404": { + "description": "Schema not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/usage_statistics_server/api/{apiVersion}/admin/settings": { + "get": { + "operationId": "settings-get", + "summary": "Get usage statistics server settings", + "description": "This endpoint requires admin access", + "tags": [ + "settings" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "apiVersion", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "v1" + ], + "default": "v1" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Current server settings", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "retentionDays", + "minimumRetentionDays", + "maximumRetentionDays" + ], + "properties": { + "retentionDays": { + "type": "integer", + "format": "int64" + }, + "minimumRetentionDays": { + "type": "integer", + "format": "int64" + }, + "maximumRetentionDays": { + "type": "integer", + "format": "int64" + } + } + } + } + } + } + } + } + } + } + } + }, + "put": { + "operationId": "settings-update", + "summary": "Update usage statistics server settings", + "description": "This endpoint requires admin access", + "tags": [ + "settings" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "retentionDays" + ], + "properties": { + "retentionDays": { + "type": "integer", + "format": "int64", + "description": "Number of days to retain reports" + } + } + } + } + } + }, + "parameters": [ + { + "name": "apiVersion", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "v1" + ], + "default": "v1" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Server settings updated", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "retentionDays" + ], + "properties": { + "retentionDays": { + "type": "integer", + "format": "int64" + } + } + } + } + } + } + } + } + } + }, + "400": { + "description": "Invalid retention value", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "error", + "message" + ], + "properties": { + "error": { + "type": "string" + }, + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/usage_statistics_server/api/{apiVersion}/admin/applications/{application}": { + "get": { + "operationId": "statistics-summary", + "summary": "Get application usage statistics summary", + "description": "This endpoint requires admin access", + "tags": [ + "statistics" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "apiVersion", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "v1" + ], + "default": "v1" + } + }, + { + "name": "application", + "in": "path", + "description": "Stable application identifier", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Application usage statistics summary", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "application", + "activeWindowDays", + "activeInstallations" + ], + "properties": { + "application": { + "type": "string" + }, + "activeWindowDays": { + "type": "integer", + "format": "int64" + }, + "activeInstallations": { + "type": "integer", + "format": "int64" + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/usage_statistics_server/api/{apiVersion}/admin/applications/{application}/metrics/{category}/{key}/distribution": { + "get": { + "operationId": "statistics-distribution", + "summary": "Get the current distribution for a metric", + "description": "This endpoint requires admin access", + "tags": [ + "statistics" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "apiVersion", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "v1" + ], + "default": "v1" + } + }, + { + "name": "application", + "in": "path", + "description": "Stable application identifier", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "category", + "in": "path", + "description": "Metric category", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "key", + "in": "path", + "description": "Metric key", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Current metric distribution", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "application", + "category", + "key", + "values" + ], + "properties": { + "application": { + "type": "string" + }, + "category": { + "type": "string" + }, + "key": { + "type": "string" + }, + "values": { + "type": "array", + "items": { + "type": "object", + "required": [ + "value", + "count" + ], + "properties": { + "value": { + "type": "object" + }, + "count": { + "type": "integer", + "format": "int64" + } + } + } + } + } + } + } + } + } + } + } + } + }, + "400": { + "description": "Metric does not support distribution aggregation", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "error", + "message" + ], + "properties": { + "error": { + "type": "string" + }, + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "404": { + "description": "Metric is not registered", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "error", + "message" + ], + "properties": { + "error": { + "type": "string" + }, + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/usage_statistics_server/api/{apiVersion}/admin/applications/{application}/metrics/{category}/{key}/numerical": { + "get": { + "operationId": "statistics-numerical", + "summary": "Get the current numerical evaluation for a metric", + "description": "This endpoint requires admin access", + "tags": [ + "statistics" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "apiVersion", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "v1" + ], + "default": "v1" + } + }, + { + "name": "application", + "in": "path", + "description": "Stable application identifier", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "category", + "in": "path", + "description": "Metric category", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "key", + "in": "path", + "description": "Metric key", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Current numerical metric evaluation", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "application", + "category", + "key", + "statistics" + ], + "properties": { + "application": { + "type": "string" + }, + "category": { + "type": "string" + }, + "key": { + "type": "string" + }, + "statistics": { + "type": "object", + "required": [ + "count", + "average", + "min", + "max", + "total" + ], + "properties": { + "count": { + "type": "integer", + "format": "int64" + }, + "average": { + "type": "number", + "format": "double", + "nullable": true + }, + "min": { + "type": "number", + "format": "double", + "nullable": true + }, + "max": { + "type": "number", + "format": "double", + "nullable": true + }, + "total": { + "type": "number", + "format": "double", + "nullable": true + } + } + } + } + } + } + } + } + } + } + } + }, + "400": { + "description": "Metric does not support numerical aggregation", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "error", + "message" + ], + "properties": { + "error": { + "type": "string" + }, + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "404": { + "description": "Metric is not registered", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "error", + "message" + ], + "properties": { + "error": { + "type": "string" + }, + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/usage_statistics_server/api/{apiVersion}/admin/applications/{application}/metrics/{category}/{key}/numerical/history": { + "get": { + "operationId": "statistics-numerical-history", + "summary": "Get historical numerical evaluation for a metric", + "description": "This endpoint requires admin access", + "tags": [ + "statistics" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "apiVersion", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "v1" + ], + "default": "v1" + } + }, + { + "name": "application", + "in": "path", + "description": "Stable application identifier", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "category", + "in": "path", + "description": "Metric category", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "key", + "in": "path", + "description": "Metric key", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "from", + "in": "query", + "description": "Optional RFC3339 lower bound", + "schema": { + "type": "string", + "default": "" + } + }, + { + "name": "to", + "in": "query", + "description": "Optional RFC3339 upper bound", + "schema": { + "type": "string", + "default": "" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Historical numerical metric evaluation", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "application", + "category", + "key", + "from", + "to", + "periods" + ], + "properties": { + "application": { + "type": "string" + }, + "category": { + "type": "string" + }, + "key": { + "type": "string" + }, + "from": { + "type": "string" + }, + "to": { + "type": "string" + }, + "periods": { + "type": "array", + "items": { + "type": "object", + "required": [ + "periodStart", + "periodEnd", + "count", + "average", + "min", + "max", + "total" + ], + "properties": { + "periodStart": { + "type": "string" + }, + "periodEnd": { + "type": "string" + }, + "count": { + "type": "integer", + "format": "int64" + }, + "average": { + "type": "number", + "format": "double", + "nullable": true + }, + "min": { + "type": "number", + "format": "double", + "nullable": true + }, + "max": { + "type": "number", + "format": "double", + "nullable": true + }, + "total": { + "type": "number", + "format": "double", + "nullable": true + } + } + } + } + } + } + } + } + } + } + } + } + }, + "400": { + "description": "Invalid date range or metric aggregation", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "error", + "message" + ], + "properties": { + "error": { + "type": "string" + }, + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "404": { + "description": "Metric is not registered", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "error", + "message" + ], + "properties": { + "error": { + "type": "string" + }, + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + } + } + }, + "tags": [] +} diff --git a/openapi-full.json b/openapi-full.json new file mode 100644 index 00000000..2000aa48 --- /dev/null +++ b/openapi-full.json @@ -0,0 +1,2000 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "usage_statistics_server-full", + "version": "0.0.1", + "description": "Receive and aggregate privacy-preserving usage statistics.", + "license": { + "name": "agpl" + } + }, + "components": { + "securitySchemes": { + "basic_auth": { + "type": "http", + "scheme": "basic" + }, + "bearer_auth": { + "type": "http", + "scheme": "bearer" + } + }, + "schemas": { + "OCSMeta": { + "type": "object", + "required": [ + "status", + "statuscode" + ], + "properties": { + "status": { + "type": "string" + }, + "statuscode": { + "type": "integer" + }, + "message": { + "type": "string" + }, + "totalitems": { + "type": "string" + }, + "itemsperpage": { + "type": "string" + } + } + } + } + }, + "paths": { + "/ocs/v2.php/apps/usage_statistics_server/api/{apiVersion}/reports": { + "post": { + "operationId": "ocs_report-create", + "summary": "Submit a usage statistics report through the Nextcloud OCS API", + "description": "This endpoint has the same Protocol v1 request semantics as the plain JSON endpoint, but wraps the response using the standard Nextcloud OCS envelope.", + "tags": [ + "ocs_report" + ], + "security": [ + {}, + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "protocolVersion", + "application", + "installationId", + "schemaVersion", + "period", + "metrics" + ], + "properties": { + "protocolVersion": { + "type": "integer", + "format": "int64", + "description": "Usage statistics protocol version" + }, + "application": { + "type": "string", + "description": "Stable application identifier" + }, + "installationId": { + "type": "string", + "description": "Stable pseudonymous installation identifier" + }, + "schemaVersion": { + "type": "integer", + "format": "int64", + "description": "Registered application schema version" + }, + "period": { + "type": "object", + "description": "RFC3339 reporting period, start inclusive and end exclusive", + "required": [ + "start", + "end" + ], + "properties": { + "start": { + "type": "string" + }, + "end": { + "type": "string" + } + } + }, + "metrics": { + "type": "array", + "description": "Aggregate metric values", + "items": { + "type": "object", + "required": [ + "category", + "key", + "type", + "value" + ], + "properties": { + "category": { + "type": "string" + }, + "key": { + "type": "string" + }, + "type": { + "type": "string" + }, + "value": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer", + "format": "int64" + }, + { + "type": "number", + "format": "double" + }, + { + "type": "boolean" + } + ] + } + } + } + } + } + } + } + } + }, + "parameters": [ + { + "name": "apiVersion", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "v1" + ], + "default": "v1" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Report accepted", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "400": { + "description": "Invalid report", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "error", + "message" + ], + "properties": { + "error": { + "type": "string" + }, + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "409": { + "description": "Conflicting report for the reporting period", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "error", + "message" + ], + "properties": { + "error": { + "type": "string" + }, + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/index.php/apps/usage_statistics_server/api/{apiVersion}/reports": { + "post": { + "operationId": "report-create", + "summary": "Submit a usage statistics report as plain Protocol v1 JSON", + "description": "Stores one validated report for one application installation and reporting period. Repeating an already accepted report with the same schema version is idempotent.", + "tags": [ + "report" + ], + "security": [ + {}, + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "protocolVersion", + "application", + "installationId", + "schemaVersion", + "period", + "metrics" + ], + "properties": { + "protocolVersion": { + "type": "integer", + "format": "int64", + "description": "Usage statistics protocol version" + }, + "application": { + "type": "string", + "description": "Stable application identifier" + }, + "installationId": { + "type": "string", + "description": "Stable pseudonymous installation identifier" + }, + "schemaVersion": { + "type": "integer", + "format": "int64", + "description": "Registered application schema version" + }, + "period": { + "type": "object", + "description": "RFC3339 reporting period, start inclusive and end exclusive", + "required": [ + "start", + "end" + ], + "properties": { + "start": { + "type": "string" + }, + "end": { + "type": "string" + } + } + }, + "metrics": { + "type": "array", + "description": "Aggregate metric values", + "items": { + "type": "object", + "required": [ + "category", + "key", + "type", + "value" + ], + "properties": { + "category": { + "type": "string" + }, + "key": { + "type": "string" + }, + "type": { + "type": "string" + }, + "value": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer", + "format": "int64" + }, + { + "type": "number", + "format": "double" + }, + { + "type": "boolean" + } + ] + } + } + } + } + } + } + } + } + }, + "parameters": [ + { + "name": "apiVersion", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "v1" + ], + "default": "v1" + } + } + ], + "responses": { + "200": { + "description": "Report accepted", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "type": "string" + } + } + } + } + } + }, + "400": { + "description": "Invalid report", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "error", + "message" + ], + "properties": { + "error": { + "type": "string" + }, + "message": { + "type": "string" + } + } + } + } + } + }, + "409": { + "description": "Conflicting report for the reporting period", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "error", + "message" + ], + "properties": { + "error": { + "type": "string" + }, + "message": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/usage_statistics_server/api/{apiVersion}/admin/schemas": { + "post": { + "operationId": "schema-create", + "summary": "Register an application metric schema", + "description": "This endpoint requires admin access", + "tags": [ + "schema" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "application", + "schemaVersion", + "metrics" + ], + "properties": { + "application": { + "type": "string", + "description": "Stable application identifier" + }, + "schemaVersion": { + "type": "integer", + "format": "int64", + "description": "Immutable schema version" + }, + "metrics": { + "type": "array", + "description": "Metric definitions", + "items": { + "type": "object", + "required": [ + "category", + "key", + "type", + "kind", + "aggregation", + "description", + "required" + ], + "properties": { + "category": { + "type": "string" + }, + "key": { + "type": "string" + }, + "type": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "aggregation": { + "type": "string" + }, + "description": { + "type": "string" + }, + "required": { + "type": "boolean" + } + } + } + } + } + } + } + } + }, + "parameters": [ + { + "name": "apiVersion", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "v1" + ], + "default": "v1" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Schema was already registered with the same definition", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "application", + "schemaVersion", + "status" + ], + "properties": { + "application": { + "type": "string" + }, + "schemaVersion": { + "type": "integer", + "format": "int64" + }, + "status": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "201": { + "description": "Schema registered", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "application", + "schemaVersion", + "status" + ], + "properties": { + "application": { + "type": "string" + }, + "schemaVersion": { + "type": "integer", + "format": "int64" + }, + "status": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "400": { + "description": "Invalid schema definition", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "error", + "message" + ], + "properties": { + "error": { + "type": "string" + }, + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "409": { + "description": "Schema version already exists with another definition", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "error", + "message" + ], + "properties": { + "error": { + "type": "string" + }, + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/usage_statistics_server/api/{apiVersion}/admin/schemas/{application}/{schemaVersion}": { + "get": { + "operationId": "schema-get", + "summary": "Get one registered application metric schema", + "description": "This endpoint requires admin access", + "tags": [ + "schema" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "apiVersion", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "v1" + ], + "default": "v1" + } + }, + { + "name": "application", + "in": "path", + "description": "Stable application identifier", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "schemaVersion", + "in": "path", + "description": "Schema version", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Registered schema definition", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "additionalProperties": { + "type": "object" + } + } + } + } + } + } + } + } + }, + "404": { + "description": "Schema not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/usage_statistics_server/api/{apiVersion}/admin/settings": { + "get": { + "operationId": "settings-get", + "summary": "Get usage statistics server settings", + "description": "This endpoint requires admin access", + "tags": [ + "settings" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "apiVersion", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "v1" + ], + "default": "v1" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Current server settings", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "retentionDays", + "minimumRetentionDays", + "maximumRetentionDays" + ], + "properties": { + "retentionDays": { + "type": "integer", + "format": "int64" + }, + "minimumRetentionDays": { + "type": "integer", + "format": "int64" + }, + "maximumRetentionDays": { + "type": "integer", + "format": "int64" + } + } + } + } + } + } + } + } + } + } + } + }, + "put": { + "operationId": "settings-update", + "summary": "Update usage statistics server settings", + "description": "This endpoint requires admin access", + "tags": [ + "settings" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "retentionDays" + ], + "properties": { + "retentionDays": { + "type": "integer", + "format": "int64", + "description": "Number of days to retain reports" + } + } + } + } + } + }, + "parameters": [ + { + "name": "apiVersion", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "v1" + ], + "default": "v1" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Server settings updated", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "retentionDays" + ], + "properties": { + "retentionDays": { + "type": "integer", + "format": "int64" + } + } + } + } + } + } + } + } + } + }, + "400": { + "description": "Invalid retention value", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "error", + "message" + ], + "properties": { + "error": { + "type": "string" + }, + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/usage_statistics_server/api/{apiVersion}/admin/applications/{application}": { + "get": { + "operationId": "statistics-summary", + "summary": "Get application usage statistics summary", + "description": "This endpoint requires admin access", + "tags": [ + "statistics" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "apiVersion", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "v1" + ], + "default": "v1" + } + }, + { + "name": "application", + "in": "path", + "description": "Stable application identifier", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Application usage statistics summary", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "application", + "activeWindowDays", + "activeInstallations" + ], + "properties": { + "application": { + "type": "string" + }, + "activeWindowDays": { + "type": "integer", + "format": "int64" + }, + "activeInstallations": { + "type": "integer", + "format": "int64" + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/usage_statistics_server/api/{apiVersion}/admin/applications/{application}/metrics/{category}/{key}/distribution": { + "get": { + "operationId": "statistics-distribution", + "summary": "Get the current distribution for a metric", + "description": "This endpoint requires admin access", + "tags": [ + "statistics" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "apiVersion", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "v1" + ], + "default": "v1" + } + }, + { + "name": "application", + "in": "path", + "description": "Stable application identifier", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "category", + "in": "path", + "description": "Metric category", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "key", + "in": "path", + "description": "Metric key", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Current metric distribution", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "application", + "category", + "key", + "values" + ], + "properties": { + "application": { + "type": "string" + }, + "category": { + "type": "string" + }, + "key": { + "type": "string" + }, + "values": { + "type": "array", + "items": { + "type": "object", + "required": [ + "value", + "count" + ], + "properties": { + "value": { + "type": "object" + }, + "count": { + "type": "integer", + "format": "int64" + } + } + } + } + } + } + } + } + } + } + } + } + }, + "400": { + "description": "Metric does not support distribution aggregation", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "error", + "message" + ], + "properties": { + "error": { + "type": "string" + }, + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "404": { + "description": "Metric is not registered", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "error", + "message" + ], + "properties": { + "error": { + "type": "string" + }, + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/usage_statistics_server/api/{apiVersion}/admin/applications/{application}/metrics/{category}/{key}/numerical": { + "get": { + "operationId": "statistics-numerical", + "summary": "Get the current numerical evaluation for a metric", + "description": "This endpoint requires admin access", + "tags": [ + "statistics" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "apiVersion", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "v1" + ], + "default": "v1" + } + }, + { + "name": "application", + "in": "path", + "description": "Stable application identifier", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "category", + "in": "path", + "description": "Metric category", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "key", + "in": "path", + "description": "Metric key", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Current numerical metric evaluation", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "application", + "category", + "key", + "statistics" + ], + "properties": { + "application": { + "type": "string" + }, + "category": { + "type": "string" + }, + "key": { + "type": "string" + }, + "statistics": { + "type": "object", + "required": [ + "count", + "average", + "min", + "max", + "total" + ], + "properties": { + "count": { + "type": "integer", + "format": "int64" + }, + "average": { + "type": "number", + "format": "double", + "nullable": true + }, + "min": { + "type": "number", + "format": "double", + "nullable": true + }, + "max": { + "type": "number", + "format": "double", + "nullable": true + }, + "total": { + "type": "number", + "format": "double", + "nullable": true + } + } + } + } + } + } + } + } + } + } + } + }, + "400": { + "description": "Metric does not support numerical aggregation", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "error", + "message" + ], + "properties": { + "error": { + "type": "string" + }, + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "404": { + "description": "Metric is not registered", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "error", + "message" + ], + "properties": { + "error": { + "type": "string" + }, + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/usage_statistics_server/api/{apiVersion}/admin/applications/{application}/metrics/{category}/{key}/numerical/history": { + "get": { + "operationId": "statistics-numerical-history", + "summary": "Get historical numerical evaluation for a metric", + "description": "This endpoint requires admin access", + "tags": [ + "statistics" + ], + "security": [ + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "apiVersion", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "v1" + ], + "default": "v1" + } + }, + { + "name": "application", + "in": "path", + "description": "Stable application identifier", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "category", + "in": "path", + "description": "Metric category", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "key", + "in": "path", + "description": "Metric key", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "from", + "in": "query", + "description": "Optional RFC3339 lower bound", + "schema": { + "type": "string", + "default": "" + } + }, + { + "name": "to", + "in": "query", + "description": "Optional RFC3339 upper bound", + "schema": { + "type": "string", + "default": "" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Historical numerical metric evaluation", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "application", + "category", + "key", + "from", + "to", + "periods" + ], + "properties": { + "application": { + "type": "string" + }, + "category": { + "type": "string" + }, + "key": { + "type": "string" + }, + "from": { + "type": "string" + }, + "to": { + "type": "string" + }, + "periods": { + "type": "array", + "items": { + "type": "object", + "required": [ + "periodStart", + "periodEnd", + "count", + "average", + "min", + "max", + "total" + ], + "properties": { + "periodStart": { + "type": "string" + }, + "periodEnd": { + "type": "string" + }, + "count": { + "type": "integer", + "format": "int64" + }, + "average": { + "type": "number", + "format": "double", + "nullable": true + }, + "min": { + "type": "number", + "format": "double", + "nullable": true + }, + "max": { + "type": "number", + "format": "double", + "nullable": true + }, + "total": { + "type": "number", + "format": "double", + "nullable": true + } + } + } + } + } + } + } + } + } + } + } + } + }, + "400": { + "description": "Invalid date range or metric aggregation", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "error", + "message" + ], + "properties": { + "error": { + "type": "string" + }, + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "404": { + "description": "Metric is not registered", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "error", + "message" + ], + "properties": { + "error": { + "type": "string" + }, + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + } + } + }, + "tags": [] +} diff --git a/openapi.json b/openapi.json new file mode 100644 index 00000000..90106987 --- /dev/null +++ b/openapi.json @@ -0,0 +1,508 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "usage_statistics_server", + "version": "0.0.1", + "description": "Receive and aggregate privacy-preserving usage statistics.", + "license": { + "name": "agpl" + } + }, + "components": { + "securitySchemes": { + "basic_auth": { + "type": "http", + "scheme": "basic" + }, + "bearer_auth": { + "type": "http", + "scheme": "bearer" + } + }, + "schemas": { + "OCSMeta": { + "type": "object", + "required": [ + "status", + "statuscode" + ], + "properties": { + "status": { + "type": "string" + }, + "statuscode": { + "type": "integer" + }, + "message": { + "type": "string" + }, + "totalitems": { + "type": "string" + }, + "itemsperpage": { + "type": "string" + } + } + } + } + }, + "paths": { + "/ocs/v2.php/apps/usage_statistics_server/api/{apiVersion}/reports": { + "post": { + "operationId": "ocs_report-create", + "summary": "Submit a usage statistics report through the Nextcloud OCS API", + "description": "This endpoint has the same Protocol v1 request semantics as the plain JSON endpoint, but wraps the response using the standard Nextcloud OCS envelope.", + "tags": [ + "ocs_report" + ], + "security": [ + {}, + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "protocolVersion", + "application", + "installationId", + "schemaVersion", + "period", + "metrics" + ], + "properties": { + "protocolVersion": { + "type": "integer", + "format": "int64", + "description": "Usage statistics protocol version" + }, + "application": { + "type": "string", + "description": "Stable application identifier" + }, + "installationId": { + "type": "string", + "description": "Stable pseudonymous installation identifier" + }, + "schemaVersion": { + "type": "integer", + "format": "int64", + "description": "Registered application schema version" + }, + "period": { + "type": "object", + "description": "RFC3339 reporting period, start inclusive and end exclusive", + "required": [ + "start", + "end" + ], + "properties": { + "start": { + "type": "string" + }, + "end": { + "type": "string" + } + } + }, + "metrics": { + "type": "array", + "description": "Aggregate metric values", + "items": { + "type": "object", + "required": [ + "category", + "key", + "type", + "value" + ], + "properties": { + "category": { + "type": "string" + }, + "key": { + "type": "string" + }, + "type": { + "type": "string" + }, + "value": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer", + "format": "int64" + }, + { + "type": "number", + "format": "double" + }, + { + "type": "boolean" + } + ] + } + } + } + } + } + } + } + } + }, + "parameters": [ + { + "name": "apiVersion", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "v1" + ], + "default": "v1" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Report accepted", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "400": { + "description": "Invalid report", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "error", + "message" + ], + "properties": { + "error": { + "type": "string" + }, + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "409": { + "description": "Conflicting report for the reporting period", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "error", + "message" + ], + "properties": { + "error": { + "type": "string" + }, + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/index.php/apps/usage_statistics_server/api/{apiVersion}/reports": { + "post": { + "operationId": "report-create", + "summary": "Submit a usage statistics report as plain Protocol v1 JSON", + "description": "Stores one validated report for one application installation and reporting period. Repeating an already accepted report with the same schema version is idempotent.", + "tags": [ + "report" + ], + "security": [ + {}, + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "protocolVersion", + "application", + "installationId", + "schemaVersion", + "period", + "metrics" + ], + "properties": { + "protocolVersion": { + "type": "integer", + "format": "int64", + "description": "Usage statistics protocol version" + }, + "application": { + "type": "string", + "description": "Stable application identifier" + }, + "installationId": { + "type": "string", + "description": "Stable pseudonymous installation identifier" + }, + "schemaVersion": { + "type": "integer", + "format": "int64", + "description": "Registered application schema version" + }, + "period": { + "type": "object", + "description": "RFC3339 reporting period, start inclusive and end exclusive", + "required": [ + "start", + "end" + ], + "properties": { + "start": { + "type": "string" + }, + "end": { + "type": "string" + } + } + }, + "metrics": { + "type": "array", + "description": "Aggregate metric values", + "items": { + "type": "object", + "required": [ + "category", + "key", + "type", + "value" + ], + "properties": { + "category": { + "type": "string" + }, + "key": { + "type": "string" + }, + "type": { + "type": "string" + }, + "value": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer", + "format": "int64" + }, + { + "type": "number", + "format": "double" + }, + { + "type": "boolean" + } + ] + } + } + } + } + } + } + } + } + }, + "parameters": [ + { + "name": "apiVersion", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "v1" + ], + "default": "v1" + } + } + ], + "responses": { + "200": { + "description": "Report accepted", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "type": "string" + } + } + } + } + } + }, + "400": { + "description": "Invalid report", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "error", + "message" + ], + "properties": { + "error": { + "type": "string" + }, + "message": { + "type": "string" + } + } + } + } + } + }, + "409": { + "description": "Conflicting report for the reporting period", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "error", + "message" + ], + "properties": { + "error": { + "type": "string" + }, + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "tags": [] +} diff --git a/src/types/openapi/openapi-administration.ts b/src/types/openapi/openapi-administration.ts new file mode 100644 index 00000000..1056f9ad --- /dev/null +++ b/src/types/openapi/openapi-administration.ts @@ -0,0 +1,728 @@ +/** + * This file was auto-generated by openapi-typescript. + * Do not make direct changes to the file. + */ + +export type paths = { + "/ocs/v2.php/apps/usage_statistics_server/api/{apiVersion}/admin/schemas": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Register an application metric schema + * @description This endpoint requires admin access + */ + post: operations["schema-create"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/ocs/v2.php/apps/usage_statistics_server/api/{apiVersion}/admin/schemas/{application}/{schemaVersion}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get one registered application metric schema + * @description This endpoint requires admin access + */ + get: operations["schema-get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/ocs/v2.php/apps/usage_statistics_server/api/{apiVersion}/admin/settings": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get usage statistics server settings + * @description This endpoint requires admin access + */ + get: operations["settings-get"]; + /** + * Update usage statistics server settings + * @description This endpoint requires admin access + */ + put: operations["settings-update"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/ocs/v2.php/apps/usage_statistics_server/api/{apiVersion}/admin/applications/{application}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get application usage statistics summary + * @description This endpoint requires admin access + */ + get: operations["statistics-summary"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/ocs/v2.php/apps/usage_statistics_server/api/{apiVersion}/admin/applications/{application}/metrics/{category}/{key}/distribution": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get the current distribution for a metric + * @description This endpoint requires admin access + */ + get: operations["statistics-distribution"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/ocs/v2.php/apps/usage_statistics_server/api/{apiVersion}/admin/applications/{application}/metrics/{category}/{key}/numerical": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get the current numerical evaluation for a metric + * @description This endpoint requires admin access + */ + get: operations["statistics-numerical"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/ocs/v2.php/apps/usage_statistics_server/api/{apiVersion}/admin/applications/{application}/metrics/{category}/{key}/numerical/history": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get historical numerical evaluation for a metric + * @description This endpoint requires admin access + */ + get: operations["statistics-numerical-history"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; +}; +export type webhooks = Record; +export type components = { + schemas: { + OCSMeta: { + status: string; + statuscode: number; + message?: string; + totalitems?: string; + itemsperpage?: string; + }; + }; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; +}; +export type $defs = Record; +export interface operations { + "schema-create": { + parameters: { + query?: never; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path: { + apiVersion: "v1"; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** @description Stable application identifier */ + application: string; + /** + * Format: int64 + * @description Immutable schema version + */ + schemaVersion: number; + /** @description Metric definitions */ + metrics: { + category: string; + key: string; + type: string; + kind: string; + aggregation: string; + description: string; + required: boolean; + }[]; + }; + }; + }; + responses: { + /** @description Schema was already registered with the same definition */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + application: string; + /** Format: int64 */ + schemaVersion: number; + status: string; + }; + }; + }; + }; + }; + /** @description Schema registered */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + application: string; + /** Format: int64 */ + schemaVersion: number; + status: string; + }; + }; + }; + }; + }; + /** @description Invalid schema definition */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + error: string; + message: string; + }; + }; + }; + }; + }; + /** @description Schema version already exists with another definition */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + error: string; + message: string; + }; + }; + }; + }; + }; + }; + }; + "schema-get": { + parameters: { + query?: never; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path: { + apiVersion: "v1"; + /** @description Stable application identifier */ + application: string; + /** @description Schema version */ + schemaVersion: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Registered schema definition */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + [key: string]: Record; + }; + }; + }; + }; + }; + /** @description Schema not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + error: string; + }; + }; + }; + }; + }; + }; + }; + "settings-get": { + parameters: { + query?: never; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path: { + apiVersion: "v1"; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Current server settings */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + /** Format: int64 */ + retentionDays: number; + /** Format: int64 */ + minimumRetentionDays: number; + /** Format: int64 */ + maximumRetentionDays: number; + }; + }; + }; + }; + }; + }; + }; + "settings-update": { + parameters: { + query?: never; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path: { + apiVersion: "v1"; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** + * Format: int64 + * @description Number of days to retain reports + */ + retentionDays: number; + }; + }; + }; + responses: { + /** @description Server settings updated */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + /** Format: int64 */ + retentionDays: number; + }; + }; + }; + }; + }; + /** @description Invalid retention value */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + error: string; + message: string; + }; + }; + }; + }; + }; + }; + }; + "statistics-summary": { + parameters: { + query?: never; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path: { + apiVersion: "v1"; + /** @description Stable application identifier */ + application: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Application usage statistics summary */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + application: string; + /** Format: int64 */ + activeWindowDays: number; + /** Format: int64 */ + activeInstallations: number; + }; + }; + }; + }; + }; + }; + }; + "statistics-distribution": { + parameters: { + query?: never; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path: { + apiVersion: "v1"; + /** @description Stable application identifier */ + application: string; + /** @description Metric category */ + category: string; + /** @description Metric key */ + key: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Current metric distribution */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + application: string; + category: string; + key: string; + values: { + value: Record; + /** Format: int64 */ + count: number; + }[]; + }; + }; + }; + }; + }; + /** @description Metric does not support distribution aggregation */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + error: string; + message: string; + }; + }; + }; + }; + }; + /** @description Metric is not registered */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + error: string; + message: string; + }; + }; + }; + }; + }; + }; + }; + "statistics-numerical": { + parameters: { + query?: never; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path: { + apiVersion: "v1"; + /** @description Stable application identifier */ + application: string; + /** @description Metric category */ + category: string; + /** @description Metric key */ + key: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Current numerical metric evaluation */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + application: string; + category: string; + key: string; + statistics: { + /** Format: int64 */ + count: number; + /** Format: double */ + average: number | null; + /** Format: double */ + min: number | null; + /** Format: double */ + max: number | null; + /** Format: double */ + total: number | null; + }; + }; + }; + }; + }; + }; + /** @description Metric does not support numerical aggregation */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + error: string; + message: string; + }; + }; + }; + }; + }; + /** @description Metric is not registered */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + error: string; + message: string; + }; + }; + }; + }; + }; + }; + }; + "statistics-numerical-history": { + parameters: { + query?: { + /** @description Optional RFC3339 lower bound */ + from?: string; + /** @description Optional RFC3339 upper bound */ + to?: string; + }; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path: { + apiVersion: "v1"; + /** @description Stable application identifier */ + application: string; + /** @description Metric category */ + category: string; + /** @description Metric key */ + key: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Historical numerical metric evaluation */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + application: string; + category: string; + key: string; + from: string; + to: string; + periods: { + periodStart: string; + periodEnd: string; + /** Format: int64 */ + count: number; + /** Format: double */ + average: number | null; + /** Format: double */ + min: number | null; + /** Format: double */ + max: number | null; + /** Format: double */ + total: number | null; + }[]; + }; + }; + }; + }; + }; + /** @description Invalid date range or metric aggregation */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + error: string; + message: string; + }; + }; + }; + }; + }; + /** @description Metric is not registered */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + error: string; + message: string; + }; + }; + }; + }; + }; + }; + }; +} diff --git a/src/types/openapi/openapi-full.ts b/src/types/openapi/openapi-full.ts new file mode 100644 index 00000000..0b4cb160 --- /dev/null +++ b/src/types/openapi/openapi-full.ts @@ -0,0 +1,944 @@ +/** + * This file was auto-generated by openapi-typescript. + * Do not make direct changes to the file. + */ + +export type paths = { + "/ocs/v2.php/apps/usage_statistics_server/api/{apiVersion}/reports": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Submit a usage statistics report through the Nextcloud OCS API + * @description This endpoint has the same Protocol v1 request semantics as the plain JSON endpoint, but wraps the response using the standard Nextcloud OCS envelope. + */ + post: operations["ocs_report-create"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/index.php/apps/usage_statistics_server/api/{apiVersion}/reports": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Submit a usage statistics report as plain Protocol v1 JSON + * @description Stores one validated report for one application installation and reporting period. Repeating an already accepted report with the same schema version is idempotent. + */ + post: operations["report-create"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/ocs/v2.php/apps/usage_statistics_server/api/{apiVersion}/admin/schemas": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Register an application metric schema + * @description This endpoint requires admin access + */ + post: operations["schema-create"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/ocs/v2.php/apps/usage_statistics_server/api/{apiVersion}/admin/schemas/{application}/{schemaVersion}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get one registered application metric schema + * @description This endpoint requires admin access + */ + get: operations["schema-get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/ocs/v2.php/apps/usage_statistics_server/api/{apiVersion}/admin/settings": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get usage statistics server settings + * @description This endpoint requires admin access + */ + get: operations["settings-get"]; + /** + * Update usage statistics server settings + * @description This endpoint requires admin access + */ + put: operations["settings-update"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/ocs/v2.php/apps/usage_statistics_server/api/{apiVersion}/admin/applications/{application}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get application usage statistics summary + * @description This endpoint requires admin access + */ + get: operations["statistics-summary"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/ocs/v2.php/apps/usage_statistics_server/api/{apiVersion}/admin/applications/{application}/metrics/{category}/{key}/distribution": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get the current distribution for a metric + * @description This endpoint requires admin access + */ + get: operations["statistics-distribution"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/ocs/v2.php/apps/usage_statistics_server/api/{apiVersion}/admin/applications/{application}/metrics/{category}/{key}/numerical": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get the current numerical evaluation for a metric + * @description This endpoint requires admin access + */ + get: operations["statistics-numerical"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/ocs/v2.php/apps/usage_statistics_server/api/{apiVersion}/admin/applications/{application}/metrics/{category}/{key}/numerical/history": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get historical numerical evaluation for a metric + * @description This endpoint requires admin access + */ + get: operations["statistics-numerical-history"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; +}; +export type webhooks = Record; +export type components = { + schemas: { + OCSMeta: { + status: string; + statuscode: number; + message?: string; + totalitems?: string; + itemsperpage?: string; + }; + }; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; +}; +export type $defs = Record; +export interface operations { + "ocs_report-create": { + parameters: { + query?: never; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path: { + apiVersion: "v1"; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** + * Format: int64 + * @description Usage statistics protocol version + */ + protocolVersion: number; + /** @description Stable application identifier */ + application: string; + /** @description Stable pseudonymous installation identifier */ + installationId: string; + /** + * Format: int64 + * @description Registered application schema version + */ + schemaVersion: number; + /** @description RFC3339 reporting period, start inclusive and end exclusive */ + period: { + start: string; + end: string; + }; + /** @description Aggregate metric values */ + metrics: { + category: string; + key: string; + type: string; + value: string | number | boolean; + }[]; + }; + }; + }; + responses: { + /** @description Report accepted */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + status: string; + }; + }; + }; + }; + }; + /** @description Invalid report */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + error: string; + message: string; + }; + }; + }; + }; + }; + /** @description Conflicting report for the reporting period */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + error: string; + message: string; + }; + }; + }; + }; + }; + }; + }; + "report-create": { + parameters: { + query?: never; + header?: never; + path: { + apiVersion: "v1"; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** + * Format: int64 + * @description Usage statistics protocol version + */ + protocolVersion: number; + /** @description Stable application identifier */ + application: string; + /** @description Stable pseudonymous installation identifier */ + installationId: string; + /** + * Format: int64 + * @description Registered application schema version + */ + schemaVersion: number; + /** @description RFC3339 reporting period, start inclusive and end exclusive */ + period: { + start: string; + end: string; + }; + /** @description Aggregate metric values */ + metrics: { + category: string; + key: string; + type: string; + value: string | number | boolean; + }[]; + }; + }; + }; + responses: { + /** @description Report accepted */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + status: string; + }; + }; + }; + /** @description Invalid report */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + message: string; + }; + }; + }; + /** @description Conflicting report for the reporting period */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + message: string; + }; + }; + }; + }; + }; + "schema-create": { + parameters: { + query?: never; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path: { + apiVersion: "v1"; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** @description Stable application identifier */ + application: string; + /** + * Format: int64 + * @description Immutable schema version + */ + schemaVersion: number; + /** @description Metric definitions */ + metrics: { + category: string; + key: string; + type: string; + kind: string; + aggregation: string; + description: string; + required: boolean; + }[]; + }; + }; + }; + responses: { + /** @description Schema was already registered with the same definition */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + application: string; + /** Format: int64 */ + schemaVersion: number; + status: string; + }; + }; + }; + }; + }; + /** @description Schema registered */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + application: string; + /** Format: int64 */ + schemaVersion: number; + status: string; + }; + }; + }; + }; + }; + /** @description Invalid schema definition */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + error: string; + message: string; + }; + }; + }; + }; + }; + /** @description Schema version already exists with another definition */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + error: string; + message: string; + }; + }; + }; + }; + }; + }; + }; + "schema-get": { + parameters: { + query?: never; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path: { + apiVersion: "v1"; + /** @description Stable application identifier */ + application: string; + /** @description Schema version */ + schemaVersion: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Registered schema definition */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + [key: string]: Record; + }; + }; + }; + }; + }; + /** @description Schema not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + error: string; + }; + }; + }; + }; + }; + }; + }; + "settings-get": { + parameters: { + query?: never; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path: { + apiVersion: "v1"; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Current server settings */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + /** Format: int64 */ + retentionDays: number; + /** Format: int64 */ + minimumRetentionDays: number; + /** Format: int64 */ + maximumRetentionDays: number; + }; + }; + }; + }; + }; + }; + }; + "settings-update": { + parameters: { + query?: never; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path: { + apiVersion: "v1"; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** + * Format: int64 + * @description Number of days to retain reports + */ + retentionDays: number; + }; + }; + }; + responses: { + /** @description Server settings updated */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + /** Format: int64 */ + retentionDays: number; + }; + }; + }; + }; + }; + /** @description Invalid retention value */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + error: string; + message: string; + }; + }; + }; + }; + }; + }; + }; + "statistics-summary": { + parameters: { + query?: never; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path: { + apiVersion: "v1"; + /** @description Stable application identifier */ + application: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Application usage statistics summary */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + application: string; + /** Format: int64 */ + activeWindowDays: number; + /** Format: int64 */ + activeInstallations: number; + }; + }; + }; + }; + }; + }; + }; + "statistics-distribution": { + parameters: { + query?: never; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path: { + apiVersion: "v1"; + /** @description Stable application identifier */ + application: string; + /** @description Metric category */ + category: string; + /** @description Metric key */ + key: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Current metric distribution */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + application: string; + category: string; + key: string; + values: { + value: Record; + /** Format: int64 */ + count: number; + }[]; + }; + }; + }; + }; + }; + /** @description Metric does not support distribution aggregation */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + error: string; + message: string; + }; + }; + }; + }; + }; + /** @description Metric is not registered */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + error: string; + message: string; + }; + }; + }; + }; + }; + }; + }; + "statistics-numerical": { + parameters: { + query?: never; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path: { + apiVersion: "v1"; + /** @description Stable application identifier */ + application: string; + /** @description Metric category */ + category: string; + /** @description Metric key */ + key: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Current numerical metric evaluation */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + application: string; + category: string; + key: string; + statistics: { + /** Format: int64 */ + count: number; + /** Format: double */ + average: number | null; + /** Format: double */ + min: number | null; + /** Format: double */ + max: number | null; + /** Format: double */ + total: number | null; + }; + }; + }; + }; + }; + }; + /** @description Metric does not support numerical aggregation */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + error: string; + message: string; + }; + }; + }; + }; + }; + /** @description Metric is not registered */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + error: string; + message: string; + }; + }; + }; + }; + }; + }; + }; + "statistics-numerical-history": { + parameters: { + query?: { + /** @description Optional RFC3339 lower bound */ + from?: string; + /** @description Optional RFC3339 upper bound */ + to?: string; + }; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path: { + apiVersion: "v1"; + /** @description Stable application identifier */ + application: string; + /** @description Metric category */ + category: string; + /** @description Metric key */ + key: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Historical numerical metric evaluation */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + application: string; + category: string; + key: string; + from: string; + to: string; + periods: { + periodStart: string; + periodEnd: string; + /** Format: int64 */ + count: number; + /** Format: double */ + average: number | null; + /** Format: double */ + min: number | null; + /** Format: double */ + max: number | null; + /** Format: double */ + total: number | null; + }[]; + }; + }; + }; + }; + }; + /** @description Invalid date range or metric aggregation */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + error: string; + message: string; + }; + }; + }; + }; + }; + /** @description Metric is not registered */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + error: string; + message: string; + }; + }; + }; + }; + }; + }; + }; +} diff --git a/src/types/openapi/openapi.ts b/src/types/openapi/openapi.ts new file mode 100644 index 00000000..fe2419ab --- /dev/null +++ b/src/types/openapi/openapi.ts @@ -0,0 +1,243 @@ +/** + * This file was auto-generated by openapi-typescript. + * Do not make direct changes to the file. + */ + +export type paths = { + "/ocs/v2.php/apps/usage_statistics_server/api/{apiVersion}/reports": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Submit a usage statistics report through the Nextcloud OCS API + * @description This endpoint has the same Protocol v1 request semantics as the plain JSON endpoint, but wraps the response using the standard Nextcloud OCS envelope. + */ + post: operations["ocs_report-create"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/index.php/apps/usage_statistics_server/api/{apiVersion}/reports": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Submit a usage statistics report as plain Protocol v1 JSON + * @description Stores one validated report for one application installation and reporting period. Repeating an already accepted report with the same schema version is idempotent. + */ + post: operations["report-create"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; +}; +export type webhooks = Record; +export type components = { + schemas: { + OCSMeta: { + status: string; + statuscode: number; + message?: string; + totalitems?: string; + itemsperpage?: string; + }; + }; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; +}; +export type $defs = Record; +export interface operations { + "ocs_report-create": { + parameters: { + query?: never; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path: { + apiVersion: "v1"; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** + * Format: int64 + * @description Usage statistics protocol version + */ + protocolVersion: number; + /** @description Stable application identifier */ + application: string; + /** @description Stable pseudonymous installation identifier */ + installationId: string; + /** + * Format: int64 + * @description Registered application schema version + */ + schemaVersion: number; + /** @description RFC3339 reporting period, start inclusive and end exclusive */ + period: { + start: string; + end: string; + }; + /** @description Aggregate metric values */ + metrics: { + category: string; + key: string; + type: string; + value: string | number | boolean; + }[]; + }; + }; + }; + responses: { + /** @description Report accepted */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + status: string; + }; + }; + }; + }; + }; + /** @description Invalid report */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + error: string; + message: string; + }; + }; + }; + }; + }; + /** @description Conflicting report for the reporting period */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + error: string; + message: string; + }; + }; + }; + }; + }; + }; + }; + "report-create": { + parameters: { + query?: never; + header?: never; + path: { + apiVersion: "v1"; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** + * Format: int64 + * @description Usage statistics protocol version + */ + protocolVersion: number; + /** @description Stable application identifier */ + application: string; + /** @description Stable pseudonymous installation identifier */ + installationId: string; + /** + * Format: int64 + * @description Registered application schema version + */ + schemaVersion: number; + /** @description RFC3339 reporting period, start inclusive and end exclusive */ + period: { + start: string; + end: string; + }; + /** @description Aggregate metric values */ + metrics: { + category: string; + key: string; + type: string; + value: string | number | boolean; + }[]; + }; + }; + }; + responses: { + /** @description Report accepted */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + status: string; + }; + }; + }; + /** @description Invalid report */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + message: string; + }; + }; + }; + /** @description Conflicting report for the reporting period */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + error: string; + message: string; + }; + }; + }; + }; + }; +} From 16c67a412bd5515e95f5c94d2f4d2eeed7059516 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Wed, 30 Sep 2026 15:19:04 -0300 Subject: [PATCH 15/16] ci: verify committed OpenAPI contracts --- .github/workflows/openapi.yml | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/.github/workflows/openapi.yml b/.github/workflows/openapi.yml index 0040ee1a..7daa86a4 100644 --- a/.github/workflows/openapi.yml +++ b/.github/workflows/openapi.yml @@ -9,7 +9,7 @@ name: OpenAPI on: pull_request permissions: - contents: write + contents: read concurrency: group: openapi-${{ github.head_ref || github.run_id }} @@ -23,9 +23,8 @@ jobs: - name: Checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: - persist-credentials: true + persist-credentials: false submodules: true - ref: ${{ github.head_ref }} - name: Get php version id: php_versions @@ -75,12 +74,11 @@ jobs: openapi-full.json src/types/openapi/*.ts - - name: Commit generated OpenAPI files + - name: Check generated OpenAPI files run: | if [[ -n "$(git status --porcelain -- openapi.json openapi-administration.json openapi-full.json src/types/openapi)" ]]; then - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add openapi.json openapi-administration.json openapi-full.json src/types/openapi - git commit -m "docs: update generated OpenAPI contracts" - git push origin HEAD:${GITHUB_HEAD_REF} + echo 'Generated OpenAPI files are out of date. Run "composer run openapi" and commit the generated files.' + git status --short -- openapi.json openapi-administration.json openapi-full.json src/types/openapi + git --no-pager diff -- openapi.json openapi-administration.json openapi-full.json src/types/openapi + exit 1 fi From 7dde401f2fdcbb1751b616319d6d7fa61cb37ef8 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Wed, 30 Sep 2026 15:20:19 -0300 Subject: [PATCH 16/16] chore: annotate generated OpenAPI types for REUSE --- REUSE.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/REUSE.toml b/REUSE.toml index 15928bac..2eb0f590 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -17,6 +17,9 @@ path = [ "package-lock.json", "phpstan.neon", "psalm.xml", + "src/types/openapi/openapi-administration.ts", + "src/types/openapi/openapi-full.ts", + "src/types/openapi/openapi.ts", "tests/integration/composer.json", "tests/integration/composer.lock", "tests/integration/features/api/schema_aggregation.feature",